Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +30 −19 Original line number Diff line number Diff line Loading @@ -52,11 +52,19 @@ internal interface DraggableHandler { * and [onStop] methods. */ internal interface DragController { /** Drag the current scene by [delta] pixels. */ fun onDrag(delta: Float) /** * Drag the current scene by [delta] pixels. * * @return the consumed [delta] */ fun onDrag(delta: Float): Float /** Starts a transition to a target scene. */ fun onStop(velocity: Float, canChangeScene: Boolean) /** * Starts a transition to a target scene. * * @return the consumed [velocity] */ fun onStop(velocity: Float, canChangeScene: Boolean): Float } internal class DraggableHandlerImpl( Loading Loading @@ -272,8 +280,10 @@ private class DragControllerImpl( * * @return the consumed delta */ override fun onDrag(delta: Float) { if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return override fun onDrag(delta: Float): Float { if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) { return 0f } swipeTransition.dragOffset += delta val (fromScene, acceleratedOffset) = Loading @@ -289,7 +299,7 @@ private class DragControllerImpl( if (result == null) { onStop(velocity = delta, canChangeScene = true) return return 0f } if ( Loading @@ -314,6 +324,8 @@ private class DragControllerImpl( updateTransition(swipeTransition) } return delta } /** Loading Loading @@ -351,10 +363,10 @@ private class DragControllerImpl( } } override fun onStop(velocity: Float, canChangeScene: Boolean) { override fun onStop(velocity: Float, canChangeScene: Boolean): Float { // The state was changed since the drag started; don't do anything. if (!isDrivingTransition || swipeTransition.isFinishing) { return return 0f } // Important: Make sure that all the code here references the current transition when Loading Loading @@ -440,7 +452,7 @@ private class DragControllerImpl( if (result == null) { // We will not animate swipeTransition.snapToScene(fromScene.key) return return 0f } val newSwipeTransition = Loading @@ -462,6 +474,9 @@ private class DragControllerImpl( animateTo(targetScene = fromScene, targetOffset = 0f) } } // The onStop animation consumes any remaining velocity. return velocity } /** Loading Loading @@ -1081,17 +1096,13 @@ internal class NestedScrollHandlerImpl( // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is // initiated in a nested child. controller.onDrag(delta = offsetAvailable) offsetAvailable }, onStop = { velocityAvailable -> val controller = dragController ?: error("Should be called after onStart") controller.onStop(velocity = velocityAvailable, canChangeScene = canChangeScene) dragController = null // The onDragStopped animation consumes any remaining velocity. velocityAvailable controller .onStop(velocity = velocityAvailable, canChangeScene = canChangeScene) .also { dragController = null } }, ) } Loading @@ -1106,7 +1117,7 @@ internal class NestedScrollHandlerImpl( internal const val OffsetVisibilityThreshold = 0.5f private object NoOpDragController : DragController { override fun onDrag(delta: Float) {} override fun onDrag(delta: Float) = 0f override fun onStop(velocity: Float, canChangeScene: Boolean) {} override fun onStop(velocity: Float, canChangeScene: Boolean) = 0f } packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +28 −13 Original line number Diff line number Diff line Loading @@ -212,7 +212,8 @@ class DraggableHandlerTest { draggableHandler: DraggableHandler, startedPosition: Offset = Offset.Zero, overSlop: Float = 0f, pointersDown: Int = 1 pointersDown: Int = 1, expectedConsumed: Boolean = true, ): DragController { val dragController = draggableHandler.onDragStarted( Loading @@ -222,17 +223,23 @@ class DraggableHandlerTest { ) // MultiPointerDraggable will always call onDelta with the initial overSlop right after dragController.onDragDelta(pixels = overSlop) dragController.onDragDelta(pixels = overSlop, expectedConsumed = expectedConsumed) return dragController } fun DragController.onDragDelta(pixels: Float) { onDrag(delta = pixels) fun DragController.onDragDelta(pixels: Float, expectedConsumed: Boolean = true) { val consumed = onDrag(delta = pixels) assertThat(consumed).isEqualTo(if (expectedConsumed) pixels else 0f) } fun DragController.onDragStopped(velocity: Float, canChangeScene: Boolean = true) { onStop(velocity, canChangeScene) fun DragController.onDragStopped( velocity: Float, canChangeScene: Boolean = true, expectedConsumed: Boolean = true ) { val consumed = onStop(velocity, canChangeScene) assertThat(consumed).isEqualTo(if (expectedConsumed) velocity else 0f) } fun NestedScrollConnection.scroll( Loading Loading @@ -360,10 +367,18 @@ class DraggableHandlerTest { @Test fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest { onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f)) onDragStarted( horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f), expectedConsumed = false, ) assertIdle(currentScene = SceneA) onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f)) onDragStarted( horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f), expectedConsumed = false, ) assertIdle(currentScene = SceneA) } Loading Loading @@ -489,19 +504,19 @@ class DraggableHandlerTest { // start accelaratedScroll and scroll over to B -> null val dragController2 = onDragStartedImmediately() dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may // still be called. Make sure that they don't crash or change the scene dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragStopped(velocity = 0f) advanceUntilIdle() assertIdle(SceneB) // These events can still come in after the animation has settled dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragStopped(velocity = 0f) assertIdle(SceneB) } Loading Loading @@ -845,7 +860,7 @@ class DraggableHandlerTest { assertThat(progress).isEqualTo(0.2f) // this should be ignored, we are scrolling now! dragController.onDragStopped(-velocityThreshold) dragController.onDragStopped(-velocityThreshold, expectedConsumed = false) assertTransition(currentScene = SceneA) nestedScroll.scroll(available = -offsetY10) Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +43 −59 Original line number Diff line number Diff line Loading @@ -49,6 +49,21 @@ import org.junit.runner.RunWith class MultiPointerDraggableTest { @get:Rule val rule = createComposeRule() private class SimpleDragController( val onDrag: () -> Unit, val onStop: () -> Unit, ) : DragController { override fun onDrag(delta: Float): Float { onDrag() return delta } override fun onStop(velocity: Float, canChangeScene: Boolean): Float { onStop() return velocity } } @Test fun cancellingPointerCallsOnDragStopped() { val size = 200f Loading @@ -70,15 +85,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } SimpleDragController( onDrag = { dragged = true }, onStop = { stopped = true }, ) }, ) ) Loading Loading @@ -142,15 +152,10 @@ class MultiPointerDraggableTest { startDragImmediately = { true }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } SimpleDragController( onDrag = { dragged = true }, onStop = { stopped = true }, ) }, ) .pointerInput(Unit) { Loading Loading @@ -218,15 +223,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } SimpleDragController( onDrag = { dragged = true }, onStop = { stopped = true }, ) }, ) ) { Loading Loading @@ -341,15 +341,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } SimpleDragController( onDrag = { dragged = true }, onStop = { stopped = true }, ) }, ) ) { Loading Loading @@ -447,15 +442,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> verticalStarted = true object : DragController { override fun onDrag(delta: Float) { verticalDragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { verticalStopped = true } } SimpleDragController( onDrag = { verticalDragged = true }, onStop = { verticalStopped = true }, ) }, ) .multiPointerDraggable( Loading @@ -464,15 +454,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> horizontalStarted = true object : DragController { override fun onDrag(delta: Float) { horizontalDragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { horizontalStopped = true } } SimpleDragController( onDrag = { horizontalDragged = true }, onStop = { horizontalStopped = true }, ) }, ) ) Loading Loading @@ -567,11 +552,10 @@ class MultiPointerDraggableTest { }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) {} override fun onStop(velocity: Float, canChangeScene: Boolean) {} } SimpleDragController( onDrag = { /* do nothing */ }, onStop = { /* do nothing */ }, ) }, ) ) {} Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +30 −19 Original line number Diff line number Diff line Loading @@ -52,11 +52,19 @@ internal interface DraggableHandler { * and [onStop] methods. */ internal interface DragController { /** Drag the current scene by [delta] pixels. */ fun onDrag(delta: Float) /** * Drag the current scene by [delta] pixels. * * @return the consumed [delta] */ fun onDrag(delta: Float): Float /** Starts a transition to a target scene. */ fun onStop(velocity: Float, canChangeScene: Boolean) /** * Starts a transition to a target scene. * * @return the consumed [velocity] */ fun onStop(velocity: Float, canChangeScene: Boolean): Float } internal class DraggableHandlerImpl( Loading Loading @@ -272,8 +280,10 @@ private class DragControllerImpl( * * @return the consumed delta */ override fun onDrag(delta: Float) { if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return override fun onDrag(delta: Float): Float { if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) { return 0f } swipeTransition.dragOffset += delta val (fromScene, acceleratedOffset) = Loading @@ -289,7 +299,7 @@ private class DragControllerImpl( if (result == null) { onStop(velocity = delta, canChangeScene = true) return return 0f } if ( Loading @@ -314,6 +324,8 @@ private class DragControllerImpl( updateTransition(swipeTransition) } return delta } /** Loading Loading @@ -351,10 +363,10 @@ private class DragControllerImpl( } } override fun onStop(velocity: Float, canChangeScene: Boolean) { override fun onStop(velocity: Float, canChangeScene: Boolean): Float { // The state was changed since the drag started; don't do anything. if (!isDrivingTransition || swipeTransition.isFinishing) { return return 0f } // Important: Make sure that all the code here references the current transition when Loading Loading @@ -440,7 +452,7 @@ private class DragControllerImpl( if (result == null) { // We will not animate swipeTransition.snapToScene(fromScene.key) return return 0f } val newSwipeTransition = Loading @@ -462,6 +474,9 @@ private class DragControllerImpl( animateTo(targetScene = fromScene, targetOffset = 0f) } } // The onStop animation consumes any remaining velocity. return velocity } /** Loading Loading @@ -1081,17 +1096,13 @@ internal class NestedScrollHandlerImpl( // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is // initiated in a nested child. controller.onDrag(delta = offsetAvailable) offsetAvailable }, onStop = { velocityAvailable -> val controller = dragController ?: error("Should be called after onStart") controller.onStop(velocity = velocityAvailable, canChangeScene = canChangeScene) dragController = null // The onDragStopped animation consumes any remaining velocity. velocityAvailable controller .onStop(velocity = velocityAvailable, canChangeScene = canChangeScene) .also { dragController = null } }, ) } Loading @@ -1106,7 +1117,7 @@ internal class NestedScrollHandlerImpl( internal const val OffsetVisibilityThreshold = 0.5f private object NoOpDragController : DragController { override fun onDrag(delta: Float) {} override fun onDrag(delta: Float) = 0f override fun onStop(velocity: Float, canChangeScene: Boolean) {} override fun onStop(velocity: Float, canChangeScene: Boolean) = 0f }
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +28 −13 Original line number Diff line number Diff line Loading @@ -212,7 +212,8 @@ class DraggableHandlerTest { draggableHandler: DraggableHandler, startedPosition: Offset = Offset.Zero, overSlop: Float = 0f, pointersDown: Int = 1 pointersDown: Int = 1, expectedConsumed: Boolean = true, ): DragController { val dragController = draggableHandler.onDragStarted( Loading @@ -222,17 +223,23 @@ class DraggableHandlerTest { ) // MultiPointerDraggable will always call onDelta with the initial overSlop right after dragController.onDragDelta(pixels = overSlop) dragController.onDragDelta(pixels = overSlop, expectedConsumed = expectedConsumed) return dragController } fun DragController.onDragDelta(pixels: Float) { onDrag(delta = pixels) fun DragController.onDragDelta(pixels: Float, expectedConsumed: Boolean = true) { val consumed = onDrag(delta = pixels) assertThat(consumed).isEqualTo(if (expectedConsumed) pixels else 0f) } fun DragController.onDragStopped(velocity: Float, canChangeScene: Boolean = true) { onStop(velocity, canChangeScene) fun DragController.onDragStopped( velocity: Float, canChangeScene: Boolean = true, expectedConsumed: Boolean = true ) { val consumed = onStop(velocity, canChangeScene) assertThat(consumed).isEqualTo(if (expectedConsumed) velocity else 0f) } fun NestedScrollConnection.scroll( Loading Loading @@ -360,10 +367,18 @@ class DraggableHandlerTest { @Test fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest { onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f)) onDragStarted( horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f), expectedConsumed = false, ) assertIdle(currentScene = SceneA) onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f)) onDragStarted( horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f), expectedConsumed = false, ) assertIdle(currentScene = SceneA) } Loading Loading @@ -489,19 +504,19 @@ class DraggableHandlerTest { // start accelaratedScroll and scroll over to B -> null val dragController2 = onDragStartedImmediately() dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may // still be called. Make sure that they don't crash or change the scene dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragStopped(velocity = 0f) advanceUntilIdle() assertIdle(SceneB) // These events can still come in after the animation has settled dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragStopped(velocity = 0f) assertIdle(SceneB) } Loading Loading @@ -845,7 +860,7 @@ class DraggableHandlerTest { assertThat(progress).isEqualTo(0.2f) // this should be ignored, we are scrolling now! dragController.onDragStopped(-velocityThreshold) dragController.onDragStopped(-velocityThreshold, expectedConsumed = false) assertTransition(currentScene = SceneA) nestedScroll.scroll(available = -offsetY10) Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +43 −59 Original line number Diff line number Diff line Loading @@ -49,6 +49,21 @@ import org.junit.runner.RunWith class MultiPointerDraggableTest { @get:Rule val rule = createComposeRule() private class SimpleDragController( val onDrag: () -> Unit, val onStop: () -> Unit, ) : DragController { override fun onDrag(delta: Float): Float { onDrag() return delta } override fun onStop(velocity: Float, canChangeScene: Boolean): Float { onStop() return velocity } } @Test fun cancellingPointerCallsOnDragStopped() { val size = 200f Loading @@ -70,15 +85,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } SimpleDragController( onDrag = { dragged = true }, onStop = { stopped = true }, ) }, ) ) Loading Loading @@ -142,15 +152,10 @@ class MultiPointerDraggableTest { startDragImmediately = { true }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } SimpleDragController( onDrag = { dragged = true }, onStop = { stopped = true }, ) }, ) .pointerInput(Unit) { Loading Loading @@ -218,15 +223,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } SimpleDragController( onDrag = { dragged = true }, onStop = { stopped = true }, ) }, ) ) { Loading Loading @@ -341,15 +341,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } SimpleDragController( onDrag = { dragged = true }, onStop = { stopped = true }, ) }, ) ) { Loading Loading @@ -447,15 +442,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> verticalStarted = true object : DragController { override fun onDrag(delta: Float) { verticalDragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { verticalStopped = true } } SimpleDragController( onDrag = { verticalDragged = true }, onStop = { verticalStopped = true }, ) }, ) .multiPointerDraggable( Loading @@ -464,15 +454,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> horizontalStarted = true object : DragController { override fun onDrag(delta: Float) { horizontalDragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { horizontalStopped = true } } SimpleDragController( onDrag = { horizontalDragged = true }, onStop = { horizontalStopped = true }, ) }, ) ) Loading Loading @@ -567,11 +552,10 @@ class MultiPointerDraggableTest { }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) {} override fun onStop(velocity: Float, canChangeScene: Boolean) {} } SimpleDragController( onDrag = { /* do nothing */ }, onStop = { /* do nothing */ }, ) }, ) ) {} Loading