summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt82
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt2
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt74
3 files changed, 120 insertions, 38 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 5d1a7c5c840f..c89dd5b7d3ec 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.fastLastOrNull
import androidx.compose.ui.util.lerp
/**
@@ -267,39 +268,58 @@ private fun <T> valueOrNull(
val sceneToValueMap = sceneToValueMap<T>(layoutImpl, key, element)
fun sceneValue(scene: SceneKey): T? = sceneToValueMap[scene]
- return when (val transition = layoutImpl.state.transitionState) {
- is TransitionState.Idle -> sceneValue(transition.currentScene)
- is TransitionState.Transition -> {
- // Note: no need to check for transition ready here given that all target values are
- // defined during composition, we should already have the correct values to interpolate
- // between here.
- val fromValue = sceneValue(transition.fromScene)
- val toValue = sceneValue(transition.toScene)
- if (fromValue != null && toValue != null) {
- if (fromValue == toValue) {
- // Optimization: avoid reading progress if the values are the same, so we don't
- // 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 sceneValue(bouncingScene)
- }
- }
+ val transition =
+ transition(layoutImpl, element, sceneToValueMap)
+ ?: return sceneValue(layoutImpl.state.transitionState.currentScene)
+ // TODO(b/311600838): Remove this. We should not have to fallback to the current
+ // scene value, but we have to because code of removed nodes can still run if they
+ // are placed with a graphics layer.
+ ?: sceneValue(scene)
- val progress =
- if (canOverflow) transition.progress
- else transition.progress.fastCoerceIn(0f, 1f)
- lerp(fromValue, toValue, progress)
+ val fromValue = sceneValue(transition.fromScene)
+ val toValue = sceneValue(transition.toScene)
+ return if (fromValue != null && toValue != null) {
+ if (fromValue == toValue) {
+ // Optimization: avoid reading progress if the values are the same, so we don't
+ // 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 sceneValue(bouncingScene)
}
- } else fromValue ?: toValue
+ }
+
+ val progress =
+ if (canOverflow) transition.progress else transition.progress.fastCoerceIn(0f, 1f)
+ lerp(fromValue, toValue, progress)
+ }
+ } else
+ fromValue
+ ?: toValue
+ // TODO(b/311600838): Remove this. We should not have to fallback to the current scene
+ // value, but we have to because code of removed nodes can still run if they are placed
+ // with a graphics layer.
+ ?: sceneValue(scene)
+}
+
+private fun transition(
+ layoutImpl: SceneTransitionLayoutImpl,
+ element: ElementKey?,
+ sceneToValueMap: Map<SceneKey, *>,
+): TransitionState.Transition? {
+ return if (element != null) {
+ layoutImpl.elements[element]?.sceneStates?.let { sceneStates ->
+ layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
+ transition.fromScene in sceneStates || transition.toScene in sceneStates
+ }
+ }
+ } else {
+ layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
+ transition.fromScene in sceneToValueMap || transition.toScene in sceneToValueMap
}
}
- // TODO(b/311600838): Remove this. We should not have to fallback to the current scene value,
- // but we have to because code of removed nodes can still run if they are placed with a graphics
- // layer.
- ?: sceneValue(scene)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index a5b6d2486168..44affd968513 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -457,7 +457,7 @@ internal abstract class BaseSceneTransitionLayoutState(
*/
internal fun startTransition(
transition: TransitionState.Transition,
- transitionKey: TransitionKey?,
+ transitionKey: TransitionKey? = null,
chain: Boolean = true,
) {
checkThread()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index e8854cf0de60..6e8b208ea9e8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -32,7 +32,12 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.util.lerp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -130,8 +135,8 @@ class AnimatedSharedAsStateTest {
// The transition lasts 64ms = 4 frames.
spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
},
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
before {
assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -189,8 +194,8 @@ class AnimatedSharedAsStateTest {
// The transition lasts 64ms = 4 frames.
spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
},
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
before {
assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -243,8 +248,8 @@ class AnimatedSharedAsStateTest {
// The transition lasts 64ms = 4 frames.
spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
},
- fromScene = TestScenes.SceneA,
- toScene = TestScenes.SceneB,
+ fromScene = SceneA,
+ toScene = SceneB,
) {
before {
assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -381,4 +386,61 @@ class AnimatedSharedAsStateTest {
}
}
}
+
+ @Test
+ fun animatedValueIsUsingLastTransition() = runTest {
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) }
+
+ val foo = ValueKey("foo")
+ val bar = ValueKey("bar")
+ val lastValues = mutableMapOf<ValueKey, MutableMap<SceneKey, Float>>()
+
+ @Composable
+ fun SceneScope.animateFloat(value: Float, key: ValueKey) {
+ val animatedValue = animateSceneFloatAsState(value, key)
+ LaunchedEffect(animatedValue) {
+ snapshotFlow { animatedValue.value }
+ .collect { lastValues.getOrPut(key) { mutableMapOf() }[sceneKey] = it }
+ }
+ }
+
+ rule.setContent {
+ SceneTransitionLayout(state) {
+ // foo goes from 0f to 100f in A => B.
+ scene(SceneA) { animateFloat(0f, foo) }
+ scene(SceneB) { animateFloat(100f, foo) }
+
+ // bar goes from 0f to 10f in C => D.
+ scene(SceneC) { animateFloat(0f, bar) }
+ scene(SceneD) { animateFloat(10f, bar) }
+ }
+ }
+
+ rule.runOnUiThread {
+ // A => B is at 30%.
+ state.startTransition(
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = { 0.3f },
+ onFinish = neverFinish(),
+ )
+ )
+
+ // C => D is at 70%.
+ state.startTransition(transition(from = SceneC, to = SceneD, progress = { 0.7f }))
+ }
+ rule.waitForIdle()
+
+ assertThat(lastValues[foo]?.get(SceneA)).isWithin(0.001f).of(30f)
+ assertThat(lastValues[foo]?.get(SceneB)).isWithin(0.001f).of(30f)
+ assertThat(lastValues[foo]?.get(SceneC)).isNull()
+ assertThat(lastValues[foo]?.get(SceneD)).isNull()
+
+ assertThat(lastValues[bar]?.get(SceneA)).isNull()
+ assertThat(lastValues[bar]?.get(SceneB)).isNull()
+ assertThat(lastValues[bar]?.get(SceneC)).isWithin(0.001f).of(7f)
+ assertThat(lastValues[bar]?.get(SceneD)).isWithin(0.001f).of(7f)
+ }
}