Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +20 −6 Original line number Diff line number Diff line Loading @@ -36,7 +36,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection import com.android.compose.nestedscroll.PriorityNestedScrollConnection import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job Loading Loading @@ -535,7 +535,7 @@ private class SceneDraggableHandler( class SceneNestedScrollHandler( private val gestureHandler: SceneGestureHandler, ) : NestedScrollHandler { override val connection: PriorityPostNestedScrollConnection = nestedScrollConnection() override val connection: PriorityNestedScrollConnection = nestedScrollConnection() private fun Offset.toAmount() = when (gestureHandler.orientation) { Loading @@ -555,7 +555,7 @@ class SceneNestedScrollHandler( Orientation.Vertical -> Offset(x = 0f, y = this) } private fun nestedScrollConnection(): PriorityPostNestedScrollConnection { private fun nestedScrollConnection(): PriorityNestedScrollConnection { // The next potential scene is calculated during the canStart var nextScene: SceneKey? = null Loading @@ -566,10 +566,24 @@ class SceneNestedScrollHandler( // moving on to the next scene. var gestureStartedOnNestedChild = false return PriorityPostNestedScrollConnection( canStart = { offsetAvailable, offsetBeforeStart -> return PriorityNestedScrollConnection( canStartPreScroll = { offsetAvailable, offsetBeforeStart -> gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero val canInterceptPreScroll = gestureHandler.isDrivingTransition && !gestureStartedOnNestedChild && offsetAvailable.toAmount() != 0f if (!canInterceptPreScroll) return@PriorityNestedScrollConnection false nextScene = gestureHandler.swipeTransitionToScene.key true }, canStartPostScroll = { offsetAvailable, offsetBeforeStart -> val amount = offsetAvailable.toAmount() if (amount == 0f) return@PriorityPostNestedScrollConnection false if (amount == 0f) return@PriorityNestedScrollConnection false gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero Loading packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt→packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +32 −16 Original line number Diff line number Diff line Loading @@ -22,17 +22,18 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.unit.Velocity /** * This [NestedScrollConnection] waits for a child to scroll ([onPostScroll]), and then decides (via * [canStart]) if it should take over scrolling. If it does, it will scroll before its children, * until [canContinueScroll] allows it. * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and * then decides (via [canStartPreScroll] or [canStartPostScroll]) if it should take over scrolling. * If it does, it will scroll before its children, until [canContinueScroll] allows it. * * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop] * after [onStart]. * * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection */ class PriorityPostNestedScrollConnection( private val canStart: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean, class PriorityNestedScrollConnection( private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean, private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean, private val canContinueScroll: () -> Boolean, private val onStart: () -> Unit, private val onScroll: (offsetAvailable: Offset) -> Offset, Loading @@ -57,26 +58,21 @@ class PriorityPostNestedScrollConnection( if ( isPriorityMode || source == NestedScrollSource.Fling || !canStart(available, offsetBeforeStart) !canStartPostScroll(available, offsetBeforeStart) ) { // The priority mode cannot start so we won't consume the available offset. return Offset.Zero } // Step 1: It's our turn! We start capturing scroll events when one of our children has an // available offset following a scroll event. isPriorityMode = true // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is // lifted (step 3b), or this object has been destroyed (step 3c). onStart() return onScroll(available) return onPriorityStart(available) } override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { if (!isPriorityMode) { if (source != NestedScrollSource.Fling) { if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) { return onPriorityStart(available) } // We want to track the amount of offset consumed before entering priority mode offsetScrolledBeforePriorityMode += available } Loading @@ -87,6 +83,11 @@ class PriorityPostNestedScrollConnection( if (!canContinueScroll()) { // Step 3a: We have lost priority and we no longer need to intercept scroll events. onPriorityStop(velocity = Velocity.Zero) // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero // We want to track the amount of offset consumed before entering priority mode offsetScrolledBeforePriorityMode += available return Offset.Zero } Loading @@ -110,8 +111,23 @@ class PriorityPostNestedScrollConnection( onPriorityStop(velocity = Velocity.Zero) } private fun onPriorityStop(velocity: Velocity): Velocity { private fun onPriorityStart(available: Offset): Offset { if (isPriorityMode) { error("This should never happen, onPriorityStart() was called when isPriorityMode") } // Step 1: It's our turn! We start capturing scroll events when one of our children has an // available offset following a scroll event. isPriorityMode = true // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is // lifted (step 3b), or this object has been destroyed (step 3c). onStart() return onScroll(available) } private fun onPriorityStop(velocity: Velocity): Velocity { // We can restart tracking the consumed offsets from scratch. offsetScrolledBeforePriorityMode = Offset.Zero Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt→packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt +37 −14 Original line number Diff line number Diff line Loading @@ -29,8 +29,9 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PriorityPostNestedScrollConnectionTest { private var canStart = false class PriorityNestedScrollConnectionTest { private var canStartPreScroll = false private var canStartPostScroll = false private var canContinueScroll = false private var isStarted = false private var lastScroll: Offset? = null Loading @@ -41,8 +42,9 @@ class PriorityPostNestedScrollConnectionTest { private var returnOnPostFling = Velocity.Zero private val scrollConnection = PriorityPostNestedScrollConnection( canStart = { _, _ -> canStart }, PriorityNestedScrollConnection( canStartPreScroll = { _, _ -> canStartPreScroll }, canStartPostScroll = { _, _ -> canStartPostScroll }, canContinueScroll = { canContinueScroll }, onStart = { isStarted = true }, onScroll = { Loading @@ -64,8 +66,29 @@ class PriorityPostNestedScrollConnectionTest { private val velocity1 = Velocity(1f, 1f) private val velocity2 = Velocity(2f, 2f) private fun startPriorityMode() { canStart = true @Test fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest { canStartPreScroll = true scrollConnection.onPostScroll( consumed = Offset.Zero, available = Offset.Zero, source = NestedScrollSource.Drag ) assertThat(isStarted).isEqualTo(false) scrollConnection.onPreFling(available = Velocity.Zero) assertThat(isStarted).isEqualTo(false) scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero) assertThat(isStarted).isEqualTo(false) scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag) assertThat(isStarted).isEqualTo(true) } private fun startPriorityModePostScroll() { canStartPostScroll = true scrollConnection.onPostScroll( consumed = Offset.Zero, available = Offset.Zero, Loading @@ -75,7 +98,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest { canStart = true canStartPostScroll = true scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag) assertThat(isStarted).isEqualTo(false) Loading @@ -86,7 +109,7 @@ class PriorityPostNestedScrollConnectionTest { scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero) assertThat(isStarted).isEqualTo(false) startPriorityMode() startPriorityModePostScroll() assertThat(isStarted).isEqualTo(true) } Loading @@ -99,13 +122,13 @@ class PriorityPostNestedScrollConnectionTest { ) assertThat(isStarted).isEqualTo(false) startPriorityMode() startPriorityModePostScroll() assertThat(isStarted).isEqualTo(true) } @Test fun step1_onPriorityModeStarted_receiveAvailableOffset() { canStart = true canStartPostScroll = true scrollConnection.onPostScroll( consumed = offset1, Loading @@ -118,7 +141,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step2_onPriorityMode_shouldContinueIfAllowed() { startPriorityMode() startPriorityModePostScroll() canContinueScroll = true scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag) Loading @@ -132,7 +155,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step3a_onPriorityMode_shouldStopIfCannotContinue() { startPriorityMode() startPriorityModePostScroll() canContinueScroll = false scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag) Loading @@ -142,7 +165,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step3b_onPriorityMode_shouldStopOnFling() = runTest { startPriorityMode() startPriorityModePostScroll() canContinueScroll = true scrollConnection.onPreFling(available = Velocity.Zero) Loading @@ -152,7 +175,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step3c_onPriorityMode_shouldStopOnReset() { startPriorityMode() startPriorityModePostScroll() canContinueScroll = true scrollConnection.reset() Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +20 −6 Original line number Diff line number Diff line Loading @@ -36,7 +36,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection import com.android.compose.nestedscroll.PriorityNestedScrollConnection import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job Loading Loading @@ -535,7 +535,7 @@ private class SceneDraggableHandler( class SceneNestedScrollHandler( private val gestureHandler: SceneGestureHandler, ) : NestedScrollHandler { override val connection: PriorityPostNestedScrollConnection = nestedScrollConnection() override val connection: PriorityNestedScrollConnection = nestedScrollConnection() private fun Offset.toAmount() = when (gestureHandler.orientation) { Loading @@ -555,7 +555,7 @@ class SceneNestedScrollHandler( Orientation.Vertical -> Offset(x = 0f, y = this) } private fun nestedScrollConnection(): PriorityPostNestedScrollConnection { private fun nestedScrollConnection(): PriorityNestedScrollConnection { // The next potential scene is calculated during the canStart var nextScene: SceneKey? = null Loading @@ -566,10 +566,24 @@ class SceneNestedScrollHandler( // moving on to the next scene. var gestureStartedOnNestedChild = false return PriorityPostNestedScrollConnection( canStart = { offsetAvailable, offsetBeforeStart -> return PriorityNestedScrollConnection( canStartPreScroll = { offsetAvailable, offsetBeforeStart -> gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero val canInterceptPreScroll = gestureHandler.isDrivingTransition && !gestureStartedOnNestedChild && offsetAvailable.toAmount() != 0f if (!canInterceptPreScroll) return@PriorityNestedScrollConnection false nextScene = gestureHandler.swipeTransitionToScene.key true }, canStartPostScroll = { offsetAvailable, offsetBeforeStart -> val amount = offsetAvailable.toAmount() if (amount == 0f) return@PriorityPostNestedScrollConnection false if (amount == 0f) return@PriorityNestedScrollConnection false gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero Loading
packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt→packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +32 −16 Original line number Diff line number Diff line Loading @@ -22,17 +22,18 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.unit.Velocity /** * This [NestedScrollConnection] waits for a child to scroll ([onPostScroll]), and then decides (via * [canStart]) if it should take over scrolling. If it does, it will scroll before its children, * until [canContinueScroll] allows it. * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and * then decides (via [canStartPreScroll] or [canStartPostScroll]) if it should take over scrolling. * If it does, it will scroll before its children, until [canContinueScroll] allows it. * * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop] * after [onStart]. * * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection */ class PriorityPostNestedScrollConnection( private val canStart: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean, class PriorityNestedScrollConnection( private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean, private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean, private val canContinueScroll: () -> Boolean, private val onStart: () -> Unit, private val onScroll: (offsetAvailable: Offset) -> Offset, Loading @@ -57,26 +58,21 @@ class PriorityPostNestedScrollConnection( if ( isPriorityMode || source == NestedScrollSource.Fling || !canStart(available, offsetBeforeStart) !canStartPostScroll(available, offsetBeforeStart) ) { // The priority mode cannot start so we won't consume the available offset. return Offset.Zero } // Step 1: It's our turn! We start capturing scroll events when one of our children has an // available offset following a scroll event. isPriorityMode = true // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is // lifted (step 3b), or this object has been destroyed (step 3c). onStart() return onScroll(available) return onPriorityStart(available) } override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { if (!isPriorityMode) { if (source != NestedScrollSource.Fling) { if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) { return onPriorityStart(available) } // We want to track the amount of offset consumed before entering priority mode offsetScrolledBeforePriorityMode += available } Loading @@ -87,6 +83,11 @@ class PriorityPostNestedScrollConnection( if (!canContinueScroll()) { // Step 3a: We have lost priority and we no longer need to intercept scroll events. onPriorityStop(velocity = Velocity.Zero) // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero // We want to track the amount of offset consumed before entering priority mode offsetScrolledBeforePriorityMode += available return Offset.Zero } Loading @@ -110,8 +111,23 @@ class PriorityPostNestedScrollConnection( onPriorityStop(velocity = Velocity.Zero) } private fun onPriorityStop(velocity: Velocity): Velocity { private fun onPriorityStart(available: Offset): Offset { if (isPriorityMode) { error("This should never happen, onPriorityStart() was called when isPriorityMode") } // Step 1: It's our turn! We start capturing scroll events when one of our children has an // available offset following a scroll event. isPriorityMode = true // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is // lifted (step 3b), or this object has been destroyed (step 3c). onStart() return onScroll(available) } private fun onPriorityStop(velocity: Velocity): Velocity { // We can restart tracking the consumed offsets from scratch. offsetScrolledBeforePriorityMode = Offset.Zero Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt→packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt +37 −14 Original line number Diff line number Diff line Loading @@ -29,8 +29,9 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PriorityPostNestedScrollConnectionTest { private var canStart = false class PriorityNestedScrollConnectionTest { private var canStartPreScroll = false private var canStartPostScroll = false private var canContinueScroll = false private var isStarted = false private var lastScroll: Offset? = null Loading @@ -41,8 +42,9 @@ class PriorityPostNestedScrollConnectionTest { private var returnOnPostFling = Velocity.Zero private val scrollConnection = PriorityPostNestedScrollConnection( canStart = { _, _ -> canStart }, PriorityNestedScrollConnection( canStartPreScroll = { _, _ -> canStartPreScroll }, canStartPostScroll = { _, _ -> canStartPostScroll }, canContinueScroll = { canContinueScroll }, onStart = { isStarted = true }, onScroll = { Loading @@ -64,8 +66,29 @@ class PriorityPostNestedScrollConnectionTest { private val velocity1 = Velocity(1f, 1f) private val velocity2 = Velocity(2f, 2f) private fun startPriorityMode() { canStart = true @Test fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest { canStartPreScroll = true scrollConnection.onPostScroll( consumed = Offset.Zero, available = Offset.Zero, source = NestedScrollSource.Drag ) assertThat(isStarted).isEqualTo(false) scrollConnection.onPreFling(available = Velocity.Zero) assertThat(isStarted).isEqualTo(false) scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero) assertThat(isStarted).isEqualTo(false) scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag) assertThat(isStarted).isEqualTo(true) } private fun startPriorityModePostScroll() { canStartPostScroll = true scrollConnection.onPostScroll( consumed = Offset.Zero, available = Offset.Zero, Loading @@ -75,7 +98,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest { canStart = true canStartPostScroll = true scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag) assertThat(isStarted).isEqualTo(false) Loading @@ -86,7 +109,7 @@ class PriorityPostNestedScrollConnectionTest { scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero) assertThat(isStarted).isEqualTo(false) startPriorityMode() startPriorityModePostScroll() assertThat(isStarted).isEqualTo(true) } Loading @@ -99,13 +122,13 @@ class PriorityPostNestedScrollConnectionTest { ) assertThat(isStarted).isEqualTo(false) startPriorityMode() startPriorityModePostScroll() assertThat(isStarted).isEqualTo(true) } @Test fun step1_onPriorityModeStarted_receiveAvailableOffset() { canStart = true canStartPostScroll = true scrollConnection.onPostScroll( consumed = offset1, Loading @@ -118,7 +141,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step2_onPriorityMode_shouldContinueIfAllowed() { startPriorityMode() startPriorityModePostScroll() canContinueScroll = true scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag) Loading @@ -132,7 +155,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step3a_onPriorityMode_shouldStopIfCannotContinue() { startPriorityMode() startPriorityModePostScroll() canContinueScroll = false scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag) Loading @@ -142,7 +165,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step3b_onPriorityMode_shouldStopOnFling() = runTest { startPriorityMode() startPriorityModePostScroll() canContinueScroll = true scrollConnection.onPreFling(available = Velocity.Zero) Loading @@ -152,7 +175,7 @@ class PriorityPostNestedScrollConnectionTest { @Test fun step3c_onPriorityMode_shouldStopOnReset() { startPriorityMode() startPriorityModePostScroll() canContinueScroll = true scrollConnection.reset() Loading