Loading packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +44 −20 Original line number Diff line number Diff line Loading @@ -95,10 +95,20 @@ interface NestedDraggable { * nested scrollable. * * This is called whenever a nested scrollable does not consume some scroll amount. If this * returns `true`, then [onDragStarted] will be called and this draggable will have priority and * returns `true`, then [onDragStarted] will be called, this draggable will have priority and * consume all future events during preScroll until the nested scroll is finished. */ fun shouldConsumeNestedScroll(sign: Float): Boolean fun shouldConsumeNestedPostScroll(sign: Float): Boolean = true /** * Whether this draggable should consume any scroll amount with the given [sign] *before* it can * be consumed by a nested scrollable. * * This is called before a nested scrollable is able to consume that scroll amount. If this * returns `true`, then [onDragStarted] will be called, this draggable will have priority and * consume all future scroll events during preScroll until the nested scroll is finished. */ fun shouldConsumeNestedPreScroll(sign: Float): Boolean = false interface Controller { /** Loading Loading @@ -540,6 +550,14 @@ private class NestedDraggableNode( } override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { val sign = available.toFloat().sign maybeCreateNewController( sign = sign, condition = { source == NestedScrollSource.UserInput && draggable.shouldConsumeNestedPreScroll(sign) }, ) val controller = nestedScrollController ?: return Offset.Zero return scrollWithOverscroll(controller, available) } Loading @@ -560,19 +578,29 @@ private class NestedDraggableNode( } val sign = offset.sign maybeCreateNewController( sign, condition = { draggable.shouldConsumeNestedPostScroll(sign) }, ) val controller = nestedScrollController ?: return Offset.Zero return scrollWithOverscroll(controller, available) } private fun maybeCreateNewController(sign: Float, condition: () -> Boolean) { if ( nestedDragsEnabled && nestedScrollController == null && // TODO(b/388231324): Remove this. !lastEventWasScrollWheel && draggable.shouldConsumeNestedScroll(sign) && lastFirstDown != null !nestedDragsEnabled || nestedScrollController != null || lastEventWasScrollWheel || lastFirstDown == null || !condition() ) { val startedPosition = checkNotNull(lastFirstDown) return } // TODO(b/382665591): Ensure that there is at least one pointer down. val pointersDownCount = pointersDown.size.coerceAtLeast(1) val pointerType = pointersDown.entries.firstOrNull()?.value val startedPosition = checkNotNull(lastFirstDown) nestedScrollController = NestedScrollController( overscrollEffect, Loading @@ -580,10 +608,6 @@ private class NestedDraggableNode( ) } val controller = nestedScrollController ?: return Offset.Zero return scrollWithOverscroll(controller, available) } private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset { return scrollWithOverscroll(offset) { controller.controller.onDrag(it.toFloat()).toOffset() Loading packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +37 −3 Original line number Diff line number Diff line Loading @@ -971,6 +971,35 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw assertThat(availableToEffectPostFling).isWithin(1f).of(100f) } @Test fun consumeNestedPreScroll() { var consumeNestedPreScroll by mutableStateOf(false) val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll }) val touchSlop = rule.setContentWithTouchSlop { Box( Modifier.fillMaxSize() .nestedDraggable(draggable, orientation) // Always consume everything so that the only way to start the drag is to // intercept preScroll events. .scrollable(rememberScrollableState { it }, orientation) ) } rule.onRoot().performTouchInput { down(center) moveBy((touchSlop + 1f).toOffset()) } assertThat(draggable.onDragStartedCalled).isFalse() consumeNestedPreScroll = true rule.onRoot().performTouchInput { moveBy(1f.toOffset()) } assertThat(draggable.onDragStartedCalled).isTrue() } private fun ComposeContentTestRule.setContentWithTouchSlop( content: @Composable () -> Unit ): Float { Loading @@ -996,7 +1025,8 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw { velocity, _ -> velocity }, private val shouldConsumeNestedScroll: (Float) -> Boolean = { true }, private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true }, private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false }, ) : NestedDraggable { var shouldStartDrag = true var onDragStartedCalled = false Loading Loading @@ -1042,8 +1072,12 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw } } override fun shouldConsumeNestedScroll(sign: Float): Boolean { return shouldConsumeNestedScroll.invoke(sign) override fun shouldConsumeNestedPostScroll(sign: Float): Boolean { return shouldConsumeNestedPostScroll.invoke(sign) } override fun shouldConsumeNestedPreScroll(sign: Float): Boolean { return shouldConsumeNestedPreScroll.invoke(sign) } } } packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +1 −1 Original line number Diff line number Diff line Loading @@ -68,7 +68,7 @@ internal class DraggableHandler( return layoutImpl.swipeDetector.detectSwipe(change) } override fun shouldConsumeNestedScroll(sign: Float): Boolean { override fun shouldConsumeNestedPostScroll(sign: Float): Boolean { return this.enabled() } Loading Loading
packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +44 −20 Original line number Diff line number Diff line Loading @@ -95,10 +95,20 @@ interface NestedDraggable { * nested scrollable. * * This is called whenever a nested scrollable does not consume some scroll amount. If this * returns `true`, then [onDragStarted] will be called and this draggable will have priority and * returns `true`, then [onDragStarted] will be called, this draggable will have priority and * consume all future events during preScroll until the nested scroll is finished. */ fun shouldConsumeNestedScroll(sign: Float): Boolean fun shouldConsumeNestedPostScroll(sign: Float): Boolean = true /** * Whether this draggable should consume any scroll amount with the given [sign] *before* it can * be consumed by a nested scrollable. * * This is called before a nested scrollable is able to consume that scroll amount. If this * returns `true`, then [onDragStarted] will be called, this draggable will have priority and * consume all future scroll events during preScroll until the nested scroll is finished. */ fun shouldConsumeNestedPreScroll(sign: Float): Boolean = false interface Controller { /** Loading Loading @@ -540,6 +550,14 @@ private class NestedDraggableNode( } override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { val sign = available.toFloat().sign maybeCreateNewController( sign = sign, condition = { source == NestedScrollSource.UserInput && draggable.shouldConsumeNestedPreScroll(sign) }, ) val controller = nestedScrollController ?: return Offset.Zero return scrollWithOverscroll(controller, available) } Loading @@ -560,19 +578,29 @@ private class NestedDraggableNode( } val sign = offset.sign maybeCreateNewController( sign, condition = { draggable.shouldConsumeNestedPostScroll(sign) }, ) val controller = nestedScrollController ?: return Offset.Zero return scrollWithOverscroll(controller, available) } private fun maybeCreateNewController(sign: Float, condition: () -> Boolean) { if ( nestedDragsEnabled && nestedScrollController == null && // TODO(b/388231324): Remove this. !lastEventWasScrollWheel && draggable.shouldConsumeNestedScroll(sign) && lastFirstDown != null !nestedDragsEnabled || nestedScrollController != null || lastEventWasScrollWheel || lastFirstDown == null || !condition() ) { val startedPosition = checkNotNull(lastFirstDown) return } // TODO(b/382665591): Ensure that there is at least one pointer down. val pointersDownCount = pointersDown.size.coerceAtLeast(1) val pointerType = pointersDown.entries.firstOrNull()?.value val startedPosition = checkNotNull(lastFirstDown) nestedScrollController = NestedScrollController( overscrollEffect, Loading @@ -580,10 +608,6 @@ private class NestedDraggableNode( ) } val controller = nestedScrollController ?: return Offset.Zero return scrollWithOverscroll(controller, available) } private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset { return scrollWithOverscroll(offset) { controller.controller.onDrag(it.toFloat()).toOffset() Loading
packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +37 −3 Original line number Diff line number Diff line Loading @@ -971,6 +971,35 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw assertThat(availableToEffectPostFling).isWithin(1f).of(100f) } @Test fun consumeNestedPreScroll() { var consumeNestedPreScroll by mutableStateOf(false) val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll }) val touchSlop = rule.setContentWithTouchSlop { Box( Modifier.fillMaxSize() .nestedDraggable(draggable, orientation) // Always consume everything so that the only way to start the drag is to // intercept preScroll events. .scrollable(rememberScrollableState { it }, orientation) ) } rule.onRoot().performTouchInput { down(center) moveBy((touchSlop + 1f).toOffset()) } assertThat(draggable.onDragStartedCalled).isFalse() consumeNestedPreScroll = true rule.onRoot().performTouchInput { moveBy(1f.toOffset()) } assertThat(draggable.onDragStartedCalled).isTrue() } private fun ComposeContentTestRule.setContentWithTouchSlop( content: @Composable () -> Unit ): Float { Loading @@ -996,7 +1025,8 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw { velocity, _ -> velocity }, private val shouldConsumeNestedScroll: (Float) -> Boolean = { true }, private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true }, private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false }, ) : NestedDraggable { var shouldStartDrag = true var onDragStartedCalled = false Loading Loading @@ -1042,8 +1072,12 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw } } override fun shouldConsumeNestedScroll(sign: Float): Boolean { return shouldConsumeNestedScroll.invoke(sign) override fun shouldConsumeNestedPostScroll(sign: Float): Boolean { return shouldConsumeNestedPostScroll.invoke(sign) } override fun shouldConsumeNestedPreScroll(sign: Float): Boolean { return shouldConsumeNestedPreScroll.invoke(sign) } } }
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +1 −1 Original line number Diff line number Diff line Loading @@ -68,7 +68,7 @@ internal class DraggableHandler( return layoutImpl.swipeDetector.detectSwipe(change) } override fun shouldConsumeNestedScroll(sign: Float): Boolean { override fun shouldConsumeNestedPostScroll(sign: Float): Boolean { return this.enabled() } Loading