Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +9 −10 Original line number Diff line number Diff line Loading @@ -381,18 +381,17 @@ private class AnimatedStateImpl<T, Delta>( // relayout/redraw for nothing. fromValue } else { // In the case of bouncing, if the value remains constant during the overscroll, we // should use the value of the scene we are bouncing around. if (!canOverflow && transition is TransitionState.HasOverscrollProperties) { val bouncingScene = transition.bouncingScene if (bouncingScene != null) { return sharedValue[bouncingScene] } } val overscrollSpec = transition.currentOverscrollSpec val progress = when { overscrollSpec == null -> { if (canOverflow) transition.progress else transition.progress.fastCoerceIn(0f, 1f) } overscrollSpec.scene == transition.toScene -> 1f else -> 0f } sharedValue.type.lerp(fromValue, toValue, progress) } } else fromValue ?: toValue Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt +55 −0 Original line number Diff line number Diff line Loading @@ -18,10 +18,13 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color Loading Loading @@ -443,4 +446,56 @@ class AnimatedSharedAsStateTest { assertThat(lastValues[bar]?.get(SceneC)).isWithin(0.001f).of(7f) assertThat(lastValues[bar]?.get(SceneD)).isWithin(0.001f).of(7f) } @Test fun animatedValueDoesNotOverscrollWhenOverscrollIsSpecified() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl( SceneA, transitions { overscroll(SceneB, Orientation.Horizontal) } ) } val key = ValueKey("foo") val lastValues = mutableMapOf<SceneKey, Float>() @Composable fun SceneScope.animateFloat(value: Float, key: ValueKey) { val animatedValue = animateSceneFloatAsState(value, key) LaunchedEffect(animatedValue) { snapshotFlow { animatedValue.value }.collect { lastValues[sceneKey] = it } } } rule.setContent { SceneTransitionLayout(state) { scene(SceneA) { animateFloat(0f, key) } scene(SceneB) { animateFloat(100f, key) } } } // Overscroll on A at -100%: value should be interpolated given that there is no overscroll // defined for scene A. var progress by mutableStateOf(-1f) rule.runOnIdle { state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress })) } rule.waitForIdle() assertThat(lastValues[SceneA]).isWithin(0.001f).of(-100f) assertThat(lastValues[SceneB]).isWithin(0.001f).of(-100f) // Middle of the transition. progress = 0.5f rule.waitForIdle() assertThat(lastValues[SceneA]).isWithin(0.001f).of(50f) assertThat(lastValues[SceneB]).isWithin(0.001f).of(50f) // Overscroll on B at 200%: value should not be interpolated given that there is an // overscroll defined for scene B. progress = 2f rule.waitForIdle() assertThat(lastValues[SceneA]).isWithin(0.001f).of(100f) assertThat(lastValues[SceneB]).isWithin(0.001f).of(100f) } } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +9 −10 Original line number Diff line number Diff line Loading @@ -381,18 +381,17 @@ private class AnimatedStateImpl<T, Delta>( // relayout/redraw for nothing. fromValue } else { // In the case of bouncing, if the value remains constant during the overscroll, we // should use the value of the scene we are bouncing around. if (!canOverflow && transition is TransitionState.HasOverscrollProperties) { val bouncingScene = transition.bouncingScene if (bouncingScene != null) { return sharedValue[bouncingScene] } } val overscrollSpec = transition.currentOverscrollSpec val progress = when { overscrollSpec == null -> { if (canOverflow) transition.progress else transition.progress.fastCoerceIn(0f, 1f) } overscrollSpec.scene == transition.toScene -> 1f else -> 0f } sharedValue.type.lerp(fromValue, toValue, progress) } } else fromValue ?: toValue Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt +55 −0 Original line number Diff line number Diff line Loading @@ -18,10 +18,13 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color Loading Loading @@ -443,4 +446,56 @@ class AnimatedSharedAsStateTest { assertThat(lastValues[bar]?.get(SceneC)).isWithin(0.001f).of(7f) assertThat(lastValues[bar]?.get(SceneD)).isWithin(0.001f).of(7f) } @Test fun animatedValueDoesNotOverscrollWhenOverscrollIsSpecified() { val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl( SceneA, transitions { overscroll(SceneB, Orientation.Horizontal) } ) } val key = ValueKey("foo") val lastValues = mutableMapOf<SceneKey, Float>() @Composable fun SceneScope.animateFloat(value: Float, key: ValueKey) { val animatedValue = animateSceneFloatAsState(value, key) LaunchedEffect(animatedValue) { snapshotFlow { animatedValue.value }.collect { lastValues[sceneKey] = it } } } rule.setContent { SceneTransitionLayout(state) { scene(SceneA) { animateFloat(0f, key) } scene(SceneB) { animateFloat(100f, key) } } } // Overscroll on A at -100%: value should be interpolated given that there is no overscroll // defined for scene A. var progress by mutableStateOf(-1f) rule.runOnIdle { state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress })) } rule.waitForIdle() assertThat(lastValues[SceneA]).isWithin(0.001f).of(-100f) assertThat(lastValues[SceneB]).isWithin(0.001f).of(-100f) // Middle of the transition. progress = 0.5f rule.waitForIdle() assertThat(lastValues[SceneA]).isWithin(0.001f).of(50f) assertThat(lastValues[SceneB]).isWithin(0.001f).of(50f) // Overscroll on B at 200%: value should not be interpolated given that there is an // overscroll defined for scene B. progress = 2f rule.waitForIdle() assertThat(lastValues[SceneA]).isWithin(0.001f).of(100f) assertThat(lastValues[SceneB]).isWithin(0.001f).of(100f) } }