diff --git a/lib/src/main/java/me/onebone/toolbar/AppBarContainer.kt b/lib/src/main/java/me/onebone/toolbar/AppBarContainer.kt index 6418d8d..b856b92 100644 --- a/lib/src/main/java/me/onebone/toolbar/AppBarContainer.kt +++ b/lib/src/main/java/me/onebone/toolbar/AppBarContainer.kt @@ -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 ) } @@ -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( diff --git a/lib/src/main/java/me/onebone/toolbar/CollapsingToolbar.kt b/lib/src/main/java/me/onebone/toolbar/CollapsingToolbar.kt index d3f9a07..d7ec737 100644 --- a/lib/src/main/java/me/onebone/toolbar/CollapsingToolbar.kt +++ b/lib/src/main/java/me/onebone/toolbar/CollapsingToolbar.kt @@ -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 @@ -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. @@ -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) { - val anim = AnimationState(offsetY.value.toFloat()) - - anim.animateTo(0f, tween(snapStrategy.expandDuration)) { - offsetY.value = value.toInt() - } - } - - @ExperimentalToolbarApi - suspend fun collapseOffset(snapStrategy: SnapStrategy, offsetY: MutableState) { - 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) { - 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 */ @@ -232,7 +160,7 @@ class CollapsingToolbarState( @Composable fun rememberCollapsingToolbarState( - initial: Int = CollapsingToolbarDefaults.INITIAL_HEIGHT + initial: Int = CollapsingToolbarDefaults.InitialHeight ): CollapsingToolbarState { return remember { CollapsingToolbarState( @@ -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( diff --git a/lib/src/main/java/me/onebone/toolbar/CollapsingToolbarScaffold.kt b/lib/src/main/java/me/onebone/toolbar/CollapsingToolbarScaffold.kt index e47b18b..6927561 100644 --- a/lib/src/main/java/me/onebone/toolbar/CollapsingToolbarScaffold.kt +++ b/lib/src/main/java/me/onebone/toolbar/CollapsingToolbarScaffold.kt @@ -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 @@ -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 @@ -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 { @@ -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 diff --git a/lib/src/main/java/me/onebone/toolbar/ScrollStrategy.kt b/lib/src/main/java/me/onebone/toolbar/ScrollStrategy.kt index f186164..ed9b38d 100644 --- a/lib/src/main/java/me/onebone/toolbar/ScrollStrategy.kt +++ b/lib/src/main/java/me/onebone/toolbar/ScrollStrategy.kt @@ -33,34 +33,34 @@ enum class ScrollStrategy { EnterAlways { override fun create( offsetY: MutableState, - 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, - 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, - toolbarState: CollapsingToolbarState, + scaffoldState: CollapsingToolbarScaffoldState, flingBehavior: FlingBehavior, snapStrategy: SnapStrategy?, ): NestedScrollConnection = - ExitUntilCollapsedNestedScrollConnection(toolbarState, flingBehavior, snapStrategy) + ExitUntilCollapsedNestedScrollConnection(scaffoldState, flingBehavior, snapStrategy) }; internal abstract fun create( offsetY: MutableState, - toolbarState: CollapsingToolbarState, + scaffoldState: CollapsingToolbarScaffoldState, flingBehavior: FlingBehavior, snapStrategy: SnapStrategy?, ): NestedScrollConnection @@ -83,12 +83,13 @@ private class ScrollDelegate( internal class EnterAlwaysNestedScrollConnection( private val offsetY: MutableState, - 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 @@ -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) } } @@ -152,12 +153,13 @@ internal class EnterAlwaysNestedScrollConnection( internal class EnterAlwaysCollapsedNestedScrollConnection( private val offsetY: MutableState, - 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 @@ -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) } } @@ -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 @@ -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) } diff --git a/lib/src/main/java/me/onebone/toolbar/SnapStrategy.kt b/lib/src/main/java/me/onebone/toolbar/SnapStrategy.kt index 8f930e2..60d0477 100644 --- a/lib/src/main/java/me/onebone/toolbar/SnapStrategy.kt +++ b/lib/src/main/java/me/onebone/toolbar/SnapStrategy.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.Immutable @Immutable class SnapStrategy( - @FloatRange(from = 0.0, to = 1.0) val edge: Float = CollapsingToolbarDefaults.EDGE, - val expandDuration: Int = CollapsingToolbarDefaults.EXPAND_DURATION, - val collapseDuration: Int = CollapsingToolbarDefaults.COLLAPSE_DURATION + @FloatRange(from = 0.0, to = 1.0) val edge: Float = CollapsingToolbarDefaults.Edge, + val expandDuration: Int = CollapsingToolbarDefaults.ExpandDuration, + val collapseDuration: Int = CollapsingToolbarDefaults.CollapseDuration ) \ No newline at end of file