Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +11 −9 Original line number Diff line number Diff line Loading @@ -362,11 +362,13 @@ internal class MultiPointerDraggableNode( pass: () -> PointerEventPass, ): PointerEvent { fun canBeConsumed(changes: List<PointerInputChange>): Boolean { // At least one pointer down AND return changes.fastAny { it.pressed } && // All pointers must be: return changes.fastAll { // A) recently pressed: even if the event has already been consumed, we can still // use the recently added finger event to determine whether to initiate dragging the // scene. changes.fastAll { // A) recently pressed: even if the event has already been consumed, we can // still use the recently added finger event to determine whether to initiate // dragging the scene. it.changedToDownIgnoreConsumed() || // B) unconsumed AND in a new position (on the current axis) it.positionChange().toFloat() != 0f Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +74 −0 Original line number Diff line number Diff line Loading @@ -120,6 +120,80 @@ class MultiPointerDraggableTest { assertThat(stopped).isTrue() } @Test fun shouldNotStartDragEventsWith0PointersDown() { val size = 200f val middle = Offset(size / 2f, size / 2f) var started = false var dragged = false var stopped = false var consumeBeforeMultiPointerDraggable = false var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop Box( Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() }) .multiPointerDraggable( orientation = Orientation.Vertical, enabled = { true }, startDragImmediately = { true }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } }, ) .pointerInput(Unit) { coroutineScope { awaitPointerEventScope { while (isActive) { val change = awaitPointerEvent().changes.first() if (consumeBeforeMultiPointerDraggable) { change.consume() } } } } } ) } // The first part of the gesture is consumed by our descendant consumeBeforeMultiPointerDraggable = true rule.onRoot().performTouchInput { down(middle) moveBy(Offset(0f, touchSlop)) } started = false dragged = false stopped = false // The next events could be consumed by us consumeBeforeMultiPointerDraggable = false rule.onRoot().performTouchInput { // The pointer is moved to a new position without reporting it updatePointerBy(0, Offset(0f, touchSlop)) // The pointer report an "up" (0 pointers down) with a new position up() } // This event should not be used to start a drag gesture assertThat(started).isFalse() assertThat(dragged).isFalse() assertThat(stopped).isFalse() } @Test fun handleDisappearingScrollableDuringAGesture() { val size = 200f Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +11 −9 Original line number Diff line number Diff line Loading @@ -362,11 +362,13 @@ internal class MultiPointerDraggableNode( pass: () -> PointerEventPass, ): PointerEvent { fun canBeConsumed(changes: List<PointerInputChange>): Boolean { // At least one pointer down AND return changes.fastAny { it.pressed } && // All pointers must be: return changes.fastAll { // A) recently pressed: even if the event has already been consumed, we can still // use the recently added finger event to determine whether to initiate dragging the // scene. changes.fastAll { // A) recently pressed: even if the event has already been consumed, we can // still use the recently added finger event to determine whether to initiate // dragging the scene. it.changedToDownIgnoreConsumed() || // B) unconsumed AND in a new position (on the current axis) it.positionChange().toFloat() != 0f Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +74 −0 Original line number Diff line number Diff line Loading @@ -120,6 +120,80 @@ class MultiPointerDraggableTest { assertThat(stopped).isTrue() } @Test fun shouldNotStartDragEventsWith0PointersDown() { val size = 200f val middle = Offset(size / 2f, size / 2f) var started = false var dragged = false var stopped = false var consumeBeforeMultiPointerDraggable = false var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop Box( Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() }) .multiPointerDraggable( orientation = Orientation.Vertical, enabled = { true }, startDragImmediately = { true }, onDragStarted = { _, _, _ -> started = true object : DragController { override fun onDrag(delta: Float) { dragged = true } override fun onStop(velocity: Float, canChangeScene: Boolean) { stopped = true } } }, ) .pointerInput(Unit) { coroutineScope { awaitPointerEventScope { while (isActive) { val change = awaitPointerEvent().changes.first() if (consumeBeforeMultiPointerDraggable) { change.consume() } } } } } ) } // The first part of the gesture is consumed by our descendant consumeBeforeMultiPointerDraggable = true rule.onRoot().performTouchInput { down(middle) moveBy(Offset(0f, touchSlop)) } started = false dragged = false stopped = false // The next events could be consumed by us consumeBeforeMultiPointerDraggable = false rule.onRoot().performTouchInput { // The pointer is moved to a new position without reporting it updatePointerBy(0, Offset(0f, touchSlop)) // The pointer report an "up" (0 pointers down) with a new position up() } // This event should not be used to start a drag gesture assertThat(started).isFalse() assertThat(dragged).isFalse() assertThat(stopped).isFalse() } @Test fun handleDisappearingScrollableDuringAGesture() { val size = 200f Loading