From 2307093dc1aef8490efb8bf5b582a67dfe4b9c70 Mon Sep 17 00:00:00 2001 From: omarmt Date: Tue, 20 Aug 2024 13:18:02 +0000 Subject: STL: Add defaultOverscrollProgressConverter in SceneTransitions [1/2] You can now define the ProgressConverter either in the SceneTransitionsBuilder or in the OverscrollBuilder. The default one will be used only if it's not defined in the OverscrollBuilder. Test: atest ElementTest Bug: 336710600 Flag: com.android.systemui.scene_container Change-Id: Ied52d2e8d93208b43850a217f7613b0c5ddb79cb --- .../com/android/compose/animation/scene/Element.kt | 5 +- .../compose/animation/scene/SceneTransitions.kt | 6 +- .../compose/animation/scene/TransitionDsl.kt | 8 +- .../compose/animation/scene/TransitionDslImpl.kt | 4 +- .../android/compose/animation/scene/ElementTest.kt | 91 ++++++++++++++++++++++ 5 files changed, 109 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index c5c3a62608b5..e6198148d265 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -1106,7 +1106,10 @@ private inline fun computeValue( val directionSign = if (transition.isUpOrLeft) -1 else 1 val isToContent = overscroll.scene == transition.toContent val linearProgress = transition.progress.let { if (isToContent) it - 1f else it } - val progress = directionSign * overscroll.progressConverter.convert(linearProgress) + val progressConverter = + overscroll.progressConverter + ?: layoutImpl.state.transitions.defaultProgressConverter + val progress = directionSign * progressConverter.convert(linearProgress) val rangeProgress = propertySpec.range?.progress(progress) ?: progress // Interpolate between the value at rest and the over scrolled value. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index a063438bb911..d35d95685d22 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -45,6 +45,7 @@ internal constructor( internal val transitionSpecs: List, internal val overscrollSpecs: List, internal val interruptionHandler: InterruptionHandler, + internal val defaultProgressConverter: ProgressConverter, ) { private val transitionCache = mutableMapOf< @@ -147,6 +148,7 @@ internal constructor( transitionSpecs = emptyList(), overscrollSpecs = emptyList(), interruptionHandler = DefaultInterruptionHandler, + defaultProgressConverter = ProgressConverter.Default, ) } } @@ -282,14 +284,14 @@ interface OverscrollSpec { * - 1, the user overscrolled by exactly the [OverscrollBuilder.distance]. * - Greater than 1, the user overscrolled more than the [OverscrollBuilder.distance]. */ - val progressConverter: ProgressConverter + val progressConverter: ProgressConverter? } internal class OverscrollSpecImpl( override val scene: SceneKey, override val orientation: Orientation, override val transformationSpec: TransformationSpecImpl, - override val progressConverter: ProgressConverter, + override val progressConverter: ProgressConverter?, ) : OverscrollSpec /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index ad1fd96c0b47..e38c849182d8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -49,6 +49,12 @@ interface SceneTransitionsBuilder { */ var interruptionHandler: InterruptionHandler + /** + * Default [ProgressConverter] used during overscroll. It lets you change a linear progress into + * a function of your choice. Defaults to [ProgressConverter.Default]. + */ + var defaultOverscrollProgressConverter: ProgressConverter + /** * Define the default animation to be played when transitioning [to] the specified content, from * any content. For the animation specification to apply only when transitioning between two @@ -217,7 +223,7 @@ interface OverscrollBuilder : BaseTransitionBuilder { * - 1, the user overscrolled by exactly the [distance]. * - Greater than 1, the user overscrolled more than the [distance]. */ - var progressConverter: ProgressConverter + var progressConverter: ProgressConverter? /** Translate the element(s) matching [matcher] by ([x], [y]) pixels. */ fun translate( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 771d1dd45e02..523e5bdd7203 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -50,12 +50,14 @@ internal fun transitionsImpl( impl.transitionSpecs, impl.transitionOverscrollSpecs, impl.interruptionHandler, + impl.defaultOverscrollProgressConverter, ) } private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { override var defaultSwipeSpec: SpringSpec = SceneTransitions.DefaultSwipeSpec override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler + override var defaultOverscrollProgressConverter: ProgressConverter = ProgressConverter.Default val transitionSpecs = mutableListOf() val transitionOverscrollSpecs = mutableListOf() @@ -271,7 +273,7 @@ internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBu } internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder { - override var progressConverter: ProgressConverter = ProgressConverter.Default + override var progressConverter: ProgressConverter? = null override fun translate( matcher: ElementMatcher, diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 34e609095e90..20b9b49c21d7 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -960,6 +960,97 @@ class ElementTest { assertThat(animatedFloat).isEqualTo(100f) } + @Test + fun elementTransitionWithDistanceDuringOverscrollWithDefaultProgressConverter() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + var animatedFloat = 0f + val state = + setupOverscrollScenario( + layoutWidth = layoutWidth, + layoutHeight = layoutHeight, + sceneTransitions = { + // Overscroll progress will be halved + defaultOverscrollProgressConverter = ProgressConverter { it / 2f } + + overscroll(SceneB, Orientation.Vertical) { + // On overscroll 100% -> Foo should translate by layoutHeight + translate(TestElements.Foo, y = { absoluteDistance }) + } + }, + firstScroll = 1f, // 100% scroll + animatedFloatRange = 0f..100f, + onAnimatedFloat = { animatedFloat = it }, + ) + + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag) + fooElement.assertTopPositionInRootIsEqualTo(0.dp) + assertThat(animatedFloat).isEqualTo(100f) + + rule.onRoot().performTouchInput { + // Scroll another 100% + moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000) + } + + val transition = assertThat(state.transitionState).isTransition() + assertThat(animatedFloat).isEqualTo(100f) + + // Scroll 200% (100% scroll + 100% overscroll) + assertThat(transition).hasProgress(2f) + assertThat(transition).hasOverscrollSpec() + + // Overscroll progress is halved, we are at 50% of the overscroll progress. + fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f) + assertThat(animatedFloat).isEqualTo(100f) + } + + @Test + fun elementTransitionWithDistanceDuringOverscrollWithOverrideDefaultProgressConverter() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + var animatedFloat = 0f + val state = + setupOverscrollScenario( + layoutWidth = layoutWidth, + layoutHeight = layoutHeight, + sceneTransitions = { + // Overscroll progress will be linear (by default) + defaultOverscrollProgressConverter = ProgressConverter { it } + + overscroll(SceneB, Orientation.Vertical) { + // This override the defaultOverscrollProgressConverter + // Overscroll progress will be halved + progressConverter = ProgressConverter { it / 2f } + // On overscroll 100% -> Foo should translate by layoutHeight + translate(TestElements.Foo, y = { absoluteDistance }) + } + }, + firstScroll = 1f, // 100% scroll + animatedFloatRange = 0f..100f, + onAnimatedFloat = { animatedFloat = it }, + ) + + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag) + fooElement.assertTopPositionInRootIsEqualTo(0.dp) + assertThat(animatedFloat).isEqualTo(100f) + + rule.onRoot().performTouchInput { + // Scroll another 100% + moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000) + } + + val transition = assertThat(state.transitionState).isTransition() + assertThat(animatedFloat).isEqualTo(100f) + + // Scroll 200% (100% scroll + 100% overscroll) + assertThat(transition).hasProgress(2f) + assertThat(transition).hasOverscrollSpec() + + // Overscroll progress is halved, we are at 50% of the overscroll progress. + fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f) + assertThat(animatedFloat).isEqualTo(100f) + } + @Test fun elementTransitionWithDistanceDuringOverscrollWithProgressConverter() { val layoutWidth = 200.dp -- cgit v1.2.3-59-g8ed1b