Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +4 −1 Original line number Diff line number Diff line Loading @@ -1106,7 +1106,10 @@ private inline fun <T> 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. Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +4 −2 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ internal constructor( internal val transitionSpecs: List<TransitionSpecImpl>, internal val overscrollSpecs: List<OverscrollSpecImpl>, internal val interruptionHandler: InterruptionHandler, internal val defaultProgressConverter: ProgressConverter, ) { private val transitionCache = mutableMapOf< Loading Loading @@ -147,6 +148,7 @@ internal constructor( transitionSpecs = emptyList(), overscrollSpecs = emptyList(), interruptionHandler = DefaultInterruptionHandler, defaultProgressConverter = ProgressConverter.Default, ) } } Loading Loading @@ -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 /** Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +7 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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( Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +3 −1 Original line number Diff line number Diff line Loading @@ -50,12 +50,14 @@ internal fun transitionsImpl( impl.transitionSpecs, impl.transitionOverscrollSpecs, impl.interruptionHandler, impl.defaultOverscrollProgressConverter, ) } private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler override var defaultOverscrollProgressConverter: ProgressConverter = ProgressConverter.Default val transitionSpecs = mutableListOf<TransitionSpecImpl>() val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>() Loading Loading @@ -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, Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +91 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +4 −1 Original line number Diff line number Diff line Loading @@ -1106,7 +1106,10 @@ private inline fun <T> 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. Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +4 −2 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ internal constructor( internal val transitionSpecs: List<TransitionSpecImpl>, internal val overscrollSpecs: List<OverscrollSpecImpl>, internal val interruptionHandler: InterruptionHandler, internal val defaultProgressConverter: ProgressConverter, ) { private val transitionCache = mutableMapOf< Loading Loading @@ -147,6 +148,7 @@ internal constructor( transitionSpecs = emptyList(), overscrollSpecs = emptyList(), interruptionHandler = DefaultInterruptionHandler, defaultProgressConverter = ProgressConverter.Default, ) } } Loading Loading @@ -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 /** Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +7 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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( Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +3 −1 Original line number Diff line number Diff line Loading @@ -50,12 +50,14 @@ internal fun transitionsImpl( impl.transitionSpecs, impl.transitionOverscrollSpecs, impl.interruptionHandler, impl.defaultOverscrollProgressConverter, ) } private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler override var defaultOverscrollProgressConverter: ProgressConverter = ProgressConverter.Default val transitionSpecs = mutableListOf<TransitionSpecImpl>() val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>() Loading Loading @@ -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, Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +91 −0 Original line number Diff line number Diff line Loading @@ -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 Loading