Skip to content

Commit

Permalink
moved snap processing from CollapsingToolbarState to CollapsingToolba…
Browse files Browse the repository at this point in the history
…rScaffoldState

+ Defaults naming refactoring
+ TODOs added
  • Loading branch information
RareScrap committed Dec 1, 2021
1 parent 6a8db15 commit 3bad446
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 104 deletions.
12 changes: 6 additions & 6 deletions lib/src/main/java/me/onebone/toolbar/AppBarContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ import kotlin.math.max
fun AppbarContainer(
modifier: Modifier = Modifier,
scrollStrategy: ScrollStrategy,
collapsingToolbarState: CollapsingToolbarState,
collapsingToolbarScaffoldState: CollapsingToolbarScaffoldState,
content: @Composable AppbarContainerScope.() -> Unit
) {
AppBarContainer(
modifier = modifier,
scrollStrategy = scrollStrategy,
collapsingToolbarState = collapsingToolbarState,
collapsingToolbarScaffoldState = collapsingToolbarScaffoldState,
content = content
)
}
Expand All @@ -75,16 +75,16 @@ fun AppBarContainer(
modifier: Modifier = Modifier,
scrollStrategy: ScrollStrategy,
/** The state of a connected collapsing toolbar */
collapsingToolbarState: CollapsingToolbarState,
collapsingToolbarScaffoldState: CollapsingToolbarScaffoldState,
content: @Composable AppbarContainerScope.() -> Unit
) {
val offsetY = remember { mutableStateOf(0) }
val flingBehavior = ScrollableDefaults.flingBehavior()
val snapStrategy = null

val (scope, measurePolicy) = remember(scrollStrategy, collapsingToolbarState) {
AppbarContainerScopeImpl(scrollStrategy.create(offsetY, collapsingToolbarState, flingBehavior, snapStrategy)) to
AppbarMeasurePolicy(scrollStrategy, collapsingToolbarState, offsetY)
val (scope, measurePolicy) = remember(scrollStrategy, collapsingToolbarScaffoldState) {
AppbarContainerScopeImpl(scrollStrategy.create(offsetY, collapsingToolbarScaffoldState, flingBehavior, snapStrategy)) to
AppbarMeasurePolicy(scrollStrategy, collapsingToolbarScaffoldState.toolbarState, offsetY)
}

Layout(
Expand Down
84 changes: 6 additions & 78 deletions lib/src/main/java/me/onebone/toolbar/CollapsingToolbar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,11 @@
package me.onebone.toolbar

import androidx.annotation.FloatRange
import androidx.compose.animation.core.AnimationState
import androidx.compose.animation.core.animateTo
import androidx.compose.animation.core.tween
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -58,7 +54,7 @@ import kotlin.math.roundToInt

@Stable
class CollapsingToolbarState(
initial: Int = CollapsingToolbarDefaults.INITIAL_HEIGHT
initial: Int = CollapsingToolbarDefaults.InitialHeight
): ScrollableState {
/**
* [height] indicates current height of the toolbar.
Expand Down Expand Up @@ -137,74 +133,6 @@ class CollapsingToolbarState(
)
fun feedScroll(value: Float): Float = dispatchRawDelta(value)

// TODO: A strange jump in snap speed is often observed
@ExperimentalToolbarApi
suspend fun expand(duration: Int = CollapsingToolbarDefaults.EXPAND_DURATION) {
val anim = AnimationState(height.toFloat())

scroll {
var prev = anim.value
anim.animateTo(maxHeight.toFloat(), tween(duration)) {
scrollBy(value - prev)
prev = value
}
}
}

// TODO: A strange jump in snap speed is often observed
@ExperimentalToolbarApi
suspend fun collapse(duration: Int = CollapsingToolbarDefaults.COLLAPSE_DURATION) {
val anim = AnimationState(height.toFloat())

scroll {
var prev = anim.value
anim.animateTo(minHeight.toFloat(), tween(duration)) {
scrollBy(value - prev)
prev = value
}
}
}

@ExperimentalToolbarApi
suspend fun expandOffset(snapStrategy: SnapStrategy, offsetY: MutableState<Int>) {
val anim = AnimationState(offsetY.value.toFloat())

anim.animateTo(0f, tween(snapStrategy.expandDuration)) {
offsetY.value = value.toInt()
}
}

@ExperimentalToolbarApi
suspend fun collapseOffset(snapStrategy: SnapStrategy, offsetY: MutableState<Int>) {
val anim = AnimationState(offsetY.value.toFloat())

anim.animateTo(-minHeight.toFloat(), tween(snapStrategy.collapseDuration)) {
offsetY.value = value.toInt()
}
}

// TODO: Is there a better solution rather OptIn ExperimentalToolbarApi?
@OptIn(ExperimentalToolbarApi::class)
internal suspend fun processSnap(strategy: SnapStrategy) {
if (progress > strategy.edge) {
expand(strategy.expandDuration)
} else {
collapse(strategy.collapseDuration)
}
}

// TODO: Is there a better solution rather OptIn ExperimentalToolbarApi?
@OptIn(ExperimentalToolbarApi::class)
internal suspend fun processOffsetSnap(snapStrategy: SnapStrategy, offsetY: MutableState<Int>) {
val offsetProgress =
1f - ((offsetY.value / (minHeight / 100f)) / 100f).absoluteValue
if (offsetProgress > snapStrategy.edge) {
expandOffset(snapStrategy, offsetY)
} else {
collapseOffset(snapStrategy, offsetY)
}
}

/**
* @return Remaining velocity after fling
*/
Expand Down Expand Up @@ -232,7 +160,7 @@ class CollapsingToolbarState(

@Composable
fun rememberCollapsingToolbarState(
initial: Int = CollapsingToolbarDefaults.INITIAL_HEIGHT
initial: Int = CollapsingToolbarDefaults.InitialHeight
): CollapsingToolbarState {
return remember {
CollapsingToolbarState(
Expand Down Expand Up @@ -260,10 +188,10 @@ fun CollapsingToolbar(
}

object CollapsingToolbarDefaults {
const val INITIAL_HEIGHT = Int.MAX_VALUE
const val EDGE = 0.5f
const val EXPAND_DURATION = 200
const val COLLAPSE_DURATION = 200
const val InitialHeight = Int.MAX_VALUE
const val Edge = 0.5f
const val ExpandDuration = 200
const val CollapseDuration = 200
}

private class CollapsingToolbarMeasurePolicy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
package me.onebone.toolbar

import android.os.Bundle
import androidx.compose.animation.core.AnimationState
import androidx.compose.animation.core.animateTo
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
Expand All @@ -34,6 +37,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.SubcomposeLayout
import kotlin.math.absoluteValue
import kotlin.math.max

@Stable
Expand All @@ -45,6 +49,78 @@ class CollapsingToolbarScaffoldState(
get() = offsetYState.value

internal val offsetYState = mutableStateOf(initialOffsetY)

// TODO: Maybe should move toolbar expand/collapse methods to CollapsingToolbarState
// but offset expand/collapse leave in CollapsingToolbarScaffoldState?

// TODO: A strange jump in snap speed is often observed
@ExperimentalToolbarApi
suspend fun expand(duration: Int = CollapsingToolbarDefaults.ExpandDuration) {
val anim = AnimationState(toolbarState.height.toFloat())

toolbarState.scroll {
var prev = anim.value
anim.animateTo(toolbarState.maxHeight.toFloat(), tween(duration)) {
scrollBy(value - prev)
prev = value
}
}
}

// TODO: A strange jump in snap speed is often observed
@ExperimentalToolbarApi
suspend fun collapse(duration: Int = CollapsingToolbarDefaults.CollapseDuration) {
val anim = AnimationState(toolbarState.height.toFloat())

toolbarState.scroll {
var prev = anim.value
anim.animateTo(toolbarState.minHeight.toFloat(), tween(duration)) {
scrollBy(value - prev)
prev = value
}
}
}

@ExperimentalToolbarApi
suspend fun expandOffset(snapStrategy: SnapStrategy) {
val anim = AnimationState(offsetYState.value.toFloat())

anim.animateTo(0f, tween(snapStrategy.expandDuration)) {
offsetYState.value = value.toInt()
}
}

@ExperimentalToolbarApi
suspend fun collapseOffset(snapStrategy: SnapStrategy) {
val anim = AnimationState(offsetYState.value.toFloat())

anim.animateTo(-toolbarState.minHeight.toFloat(), tween(snapStrategy.collapseDuration)) {
offsetYState.value = value.toInt()
}
}

// TODO: Is there a better solution rather OptIn ExperimentalToolbarApi?
@OptIn(ExperimentalToolbarApi::class)
internal suspend fun processSnap(strategy: SnapStrategy) {
if (toolbarState.progress > strategy.edge) {
expand(strategy.expandDuration)
} else {
collapse(strategy.collapseDuration)
}
}

// TODO: Is there a better solution rather OptIn ExperimentalToolbarApi?
@OptIn(ExperimentalToolbarApi::class)
internal suspend fun processOffsetSnap(snapStrategy: SnapStrategy) {
// TODO: Refactor ugly math
val offsetProgress =
1f - ((offsetYState.value / (toolbarState.minHeight / 100f)) / 100f).absoluteValue
if (offsetProgress > snapStrategy.edge) {
expandOffset(snapStrategy)
} else {
collapseOffset(snapStrategy)
}
}
}

private class CollapsingToolbarScaffoldStateSaver: Saver<CollapsingToolbarScaffoldState, Bundle> {
Expand Down Expand Up @@ -83,7 +159,8 @@ fun CollapsingToolbarScaffold(
val flingBehavior = ScrollableDefaults.flingBehavior()

val nestedScrollConnection = remember(scrollStrategy, state) {
scrollStrategy.create(state.offsetYState, state.toolbarState, flingBehavior, snapStrategy)
// TODO by RareScrap: Should we make offsetYState public and pass just state?
scrollStrategy.create(state.offsetYState, state, flingBehavior, snapStrategy)
}

val toolbarState = state.toolbarState
Expand Down
35 changes: 19 additions & 16 deletions lib/src/main/java/me/onebone/toolbar/ScrollStrategy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,34 @@ enum class ScrollStrategy {
EnterAlways {
override fun create(
offsetY: MutableState<Int>,
toolbarState: CollapsingToolbarState,
scaffoldState: CollapsingToolbarScaffoldState,
flingBehavior: FlingBehavior,
snapStrategy: SnapStrategy?,
): NestedScrollConnection =
EnterAlwaysNestedScrollConnection(offsetY, toolbarState, flingBehavior, snapStrategy)
EnterAlwaysNestedScrollConnection(offsetY, scaffoldState, flingBehavior, snapStrategy)
},
EnterAlwaysCollapsed {
override fun create(
offsetY: MutableState<Int>,
toolbarState: CollapsingToolbarState,
scaffoldState: CollapsingToolbarScaffoldState,
flingBehavior: FlingBehavior,
snapStrategy: SnapStrategy?,
): NestedScrollConnection =
EnterAlwaysCollapsedNestedScrollConnection(offsetY, toolbarState, flingBehavior, snapStrategy)
EnterAlwaysCollapsedNestedScrollConnection(offsetY, scaffoldState, flingBehavior, snapStrategy)
},
ExitUntilCollapsed {
override fun create(
offsetY: MutableState<Int>,
toolbarState: CollapsingToolbarState,
scaffoldState: CollapsingToolbarScaffoldState,
flingBehavior: FlingBehavior,
snapStrategy: SnapStrategy?,
): NestedScrollConnection =
ExitUntilCollapsedNestedScrollConnection(toolbarState, flingBehavior, snapStrategy)
ExitUntilCollapsedNestedScrollConnection(scaffoldState, flingBehavior, snapStrategy)
};

internal abstract fun create(
offsetY: MutableState<Int>,
toolbarState: CollapsingToolbarState,
scaffoldState: CollapsingToolbarScaffoldState,
flingBehavior: FlingBehavior,
snapStrategy: SnapStrategy?,
): NestedScrollConnection
Expand All @@ -83,12 +83,13 @@ private class ScrollDelegate(

internal class EnterAlwaysNestedScrollConnection(
private val offsetY: MutableState<Int>,
private val toolbarState: CollapsingToolbarState,
private val scaffoldState: CollapsingToolbarScaffoldState,
private val flingBehavior: FlingBehavior,
private val snapStrategy: SnapStrategy?
): NestedScrollConnection {
private val scrollDelegate = ScrollDelegate(offsetY)
private val tracker = RelativeVelocityTracker(CurrentTimeProviderImpl())
private val toolbarState = scaffoldState.toolbarState

override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val dy = available.y
Expand Down Expand Up @@ -140,9 +141,9 @@ internal class EnterAlwaysNestedScrollConnection(
// When the toolbar is hiding, it does it through changing the offset and does not
// change its height, so we must process not the snap of the toolbar, but the
// snap of its offset.
toolbarState.processOffsetSnap(it, offsetY)
scaffoldState.processOffsetSnap(it)
} else {
toolbarState.processSnap(it)
scaffoldState.processSnap(it)
}
}

Expand All @@ -152,12 +153,13 @@ internal class EnterAlwaysNestedScrollConnection(

internal class EnterAlwaysCollapsedNestedScrollConnection(
private val offsetY: MutableState<Int>,
private val toolbarState: CollapsingToolbarState,
private val scaffoldState: CollapsingToolbarScaffoldState,
private val flingBehavior: FlingBehavior,
private val snapStrategy: SnapStrategy?,
): NestedScrollConnection {
private val scrollDelegate = ScrollDelegate(offsetY)
private val tracker = RelativeVelocityTracker(CurrentTimeProviderImpl())
private val toolbarState = scaffoldState.toolbarState

override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val dy = available.y
Expand Down Expand Up @@ -211,14 +213,14 @@ internal class EnterAlwaysCollapsedNestedScrollConnection(

// TODO: Cancel expand/collapse animation inside onPreScroll
snapStrategy?.let {
val isToolbarChangingOffset = offsetY.value != 0//toolbarState.progress == 0f
val isToolbarChangingOffset = offsetY.value != 0
if (isToolbarChangingOffset) {
// When the toolbar is hiding, it does it through changing the offset and does not
// change its height, so we must process not the snap of the toolbar, but the
// snap of its offset.
toolbarState.processOffsetSnap(it, offsetY)
scaffoldState.processOffsetSnap(it)
} else {
toolbarState.processSnap(it)
scaffoldState.processSnap(it)
}
}

Expand All @@ -227,11 +229,12 @@ internal class EnterAlwaysCollapsedNestedScrollConnection(
}

internal class ExitUntilCollapsedNestedScrollConnection(
private val toolbarState: CollapsingToolbarState,
private val scaffoldState: CollapsingToolbarScaffoldState,
private val flingBehavior: FlingBehavior,
private val snapStrategy: SnapStrategy?
): NestedScrollConnection {
private val tracker = RelativeVelocityTracker(CurrentTimeProviderImpl())
private val toolbarState = scaffoldState.toolbarState

override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val dy = available.y
Expand Down Expand Up @@ -284,7 +287,7 @@ internal class ExitUntilCollapsedNestedScrollConnection(
}

// TODO: Cancel expand/collapse animation inside onPreScroll
snapStrategy?.let { toolbarState.processSnap(it) }
snapStrategy?.let { scaffoldState.processSnap(it) }

return available.copy(y = available.y - left)
}
Expand Down
Loading

0 comments on commit 3bad446

Please sign in to comment.