diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2021-03-16 15:22:44 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2021-03-16 15:22:44 +0000 |
commit | 92d5b6729ff33e3557ca2daed9ed8b96c8c0d636 (patch) | |
tree | 36043d0680657c7e3310d503de6c96aed3bdf0bb | |
parent | 58fe84235c628be3a321e5e1275d15d0a344518a (diff) | |
parent | 95605fcdbca4aec2ad511d3ebe5261f3e8c9e4e5 (diff) | |
download | support-92d5b6729ff33e3557ca2daed9ed8b96c8c0d636.tar.gz |
Merge "Properly reverse velocity and deltas for flingbehaviour when reverseDirection=true" into androidx-main
2 files changed, 268 insertions, 2 deletions
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt index ab1d15d7898..bcc20f7b132 100644 --- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt +++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt @@ -149,6 +149,61 @@ class ScrollableTest { @Test @OptIn(ExperimentalTestApi::class) + fun scrollable_horizontalScroll_reverse() = runBlockingWithManualClock { clock -> + var total = 0f + val controller = ScrollableState( + consumeScrollDelta = { + total += it + it + } + ) + setScrollableContent { + Modifier.scrollable( + reverseDirection = true, + state = controller, + orientation = Orientation.Horizontal + ) + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + this.swipe( + start = this.center, + end = Offset(this.center.x + 100f, this.center.y), + durationMillis = 100 + ) + } + advanceClockWhileAwaitersExist(clock) + + val lastTotal = rule.runOnIdle { + assertThat(total).isLessThan(0) + total + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + this.swipe( + start = this.center, + end = Offset(this.center.x, this.center.y + 100f), + durationMillis = 100 + ) + } + advanceClockWhileAwaitersExist(clock) + + rule.runOnIdle { + assertThat(total).isEqualTo(lastTotal) + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + this.swipe( + start = this.center, + end = Offset(this.center.x - 100f, this.center.y), + durationMillis = 100 + ) + } + advanceClockWhileAwaitersExist(clock) + rule.runOnIdle { + assertThat(total).isLessThan(0.01f) + } + } + + @Test + @OptIn(ExperimentalTestApi::class) fun scrollable_verticalScroll() = runBlockingWithManualClock { clock -> var total = 0f val controller = ScrollableState( @@ -203,6 +258,61 @@ class ScrollableTest { @Test @OptIn(ExperimentalTestApi::class) + fun scrollable_verticalScroll_reversed() = runBlockingWithManualClock { clock -> + var total = 0f + val controller = ScrollableState( + consumeScrollDelta = { + total += it + it + } + ) + setScrollableContent { + Modifier.scrollable( + reverseDirection = true, + state = controller, + orientation = Orientation.Vertical + ) + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + this.swipe( + start = this.center, + end = Offset(this.center.x, this.center.y + 100f), + durationMillis = 100 + ) + } + advanceClockWhileAwaitersExist(clock) + + val lastTotal = rule.runOnIdle { + assertThat(total).isLessThan(0) + total + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + this.swipe( + start = this.center, + end = Offset(this.center.x + 100f, this.center.y), + durationMillis = 100 + ) + } + advanceClockWhileAwaitersExist(clock) + + rule.runOnIdle { + assertThat(total).isEqualTo(lastTotal) + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + this.swipe( + start = this.center, + end = Offset(this.center.x, this.center.y - 100f), + durationMillis = 100 + ) + } + advanceClockWhileAwaitersExist(clock) + rule.runOnIdle { + assertThat(total).isLessThan(0.01f) + } + } + + @Test + @OptIn(ExperimentalTestApi::class) fun scrollable_disabledWontCallLambda() = runBlockingWithManualClock { clock -> val enabled = mutableStateOf(true) var total = 0f @@ -910,6 +1020,162 @@ class ScrollableTest { } @Test + fun scrollable_flingBehaviourCalled() { + var total = 0f + val controller = ScrollableState( + consumeScrollDelta = { + total += it + it + } + ) + var flingCalled = 0 + var flingVelocity: Float = Float.MAX_VALUE + val flingBehaviour = object : FlingBehavior { + override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { + flingCalled++ + flingVelocity = initialVelocity + return 0f + } + } + setScrollableContent { + Modifier.scrollable( + state = controller, + flingBehavior = flingBehaviour, + orientation = Orientation.Horizontal + ) + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + swipeWithVelocity( + this.center, + this.center + Offset(115f, 0f), + endVelocity = 1000f + ) + } + assertThat(flingCalled).isEqualTo(1) + assertThat(flingVelocity).isWithin(5f).of(1000f) + } + + @Test + fun scrollable_flingBehaviourCalled_reversed() { + var total = 0f + val controller = ScrollableState( + consumeScrollDelta = { + total += it + it + } + ) + var flingCalled = 0 + var flingVelocity: Float = Float.MAX_VALUE + val flingBehaviour = object : FlingBehavior { + override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { + flingCalled++ + flingVelocity = initialVelocity + return 0f + } + } + setScrollableContent { + Modifier.scrollable( + state = controller, + reverseDirection = true, + flingBehavior = flingBehaviour, + orientation = Orientation.Horizontal + ) + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + swipeWithVelocity( + this.center, + this.center + Offset(115f, 0f), + endVelocity = 1000f + ) + } + assertThat(flingCalled).isEqualTo(1) + assertThat(flingVelocity).isWithin(5f).of(-1000f) + } + + @Test + fun scrollable_flingBehaviourCalled_correctScope() { + var total = 0f + val controller = ScrollableState( + consumeScrollDelta = { + total += it + it + } + ) + val flingBehaviour = object : FlingBehavior { + override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { + scrollBy(123f) + return 0f + } + } + setScrollableContent { + Modifier.scrollable( + state = controller, + flingBehavior = flingBehaviour, + orientation = Orientation.Horizontal + ) + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + down(center) + moveBy(Offset(x = 100f, y = 0f)) + } + + val prevTotal = rule.runOnIdle { + assertThat(total).isGreaterThan(0f) + total + } + + rule.onNodeWithTag(scrollableBoxTag).performGesture { + up() + } + + rule.runOnIdle { + assertThat(total).isEqualTo(prevTotal + 123) + } + } + + @Test + fun scrollable_flingBehaviourCalled_reversed_correctScope() { + var total = 0f + val controller = ScrollableState( + consumeScrollDelta = { + total += it + it + } + ) + val flingBehaviour = object : FlingBehavior { + override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { + scrollBy(123f) + return 0f + } + } + setScrollableContent { + Modifier.scrollable( + state = controller, + reverseDirection = true, + flingBehavior = flingBehaviour, + orientation = Orientation.Horizontal + ) + } + rule.onNodeWithTag(scrollableBoxTag).performGesture { + down(center) + moveBy(Offset(x = 100f, y = 0f)) + } + + val prevTotal = rule.runOnIdle { + assertThat(total).isLessThan(0f) + total + } + + rule.onNodeWithTag(scrollableBoxTag).performGesture { + up() + } + + rule.runOnIdle { + assertThat(total).isEqualTo(prevTotal + 123) + } + } + + @Test fun testInspectorValue() { val controller = ScrollableState( consumeScrollDelta = { it } diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt index 4b3d18af152..21b9c10051a 100644 --- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt +++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt @@ -212,7 +212,7 @@ private class ScrollingLogic( // come up with the better threshold, but we need it since spline curve gives us NaNs scrollableState.scroll { val outerScopeScroll: (Float) -> Float = - { delta -> this.dispatchScroll(delta, NestedScrollSource.Fling) } + { delta -> this.dispatchScroll(delta.reverseIfNeeded(), NestedScrollSource.Fling) } val scope = object : ScrollScope { override fun scrollBy(pixels: Float): Float { return outerScopeScroll.invoke(pixels) @@ -220,7 +220,7 @@ private class ScrollingLogic( } with(scope) { with(flingBehavior) { - result = performFling(available.toFloat()).toVelocity() + result = performFling(available.toFloat().reverseIfNeeded()).toVelocity() } } } |