diff options
8 files changed, 52 insertions, 6 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 1d2c053b3c76..b264a7ef0da3 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 @@ -17,6 +17,7 @@ package com.android.compose.animation.scene import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf @@ -47,6 +48,7 @@ import com.android.compose.ui.util.lerp import kotlinx.coroutines.launch /** An element on screen, that can be composed in one or more scenes. */ +@Stable internal class Element(val key: ElementKey) { /** * The last values of this element, coming from any scene. Note that this value will be unstable @@ -91,6 +93,7 @@ internal class Element(val key: ElementKey) { } /** The target values of this element in a given scene. */ + @Stable class TargetValues(val scene: SceneKey) { val lastValues = Values() @@ -108,6 +111,7 @@ internal class Element(val key: ElementKey) { } /** A shared value of this element. */ + @Stable class SharedValue<T>(val key: ValueKey, initialValue: T) { var value by mutableStateOf(initialValue) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt index 5b752eb4e900..84d3b8647d6c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt @@ -17,11 +17,13 @@ package com.android.compose.animation.scene import androidx.annotation.VisibleForTesting +import androidx.compose.runtime.Stable /** * A base class to create unique keys, associated to an [identity] that is used to check the * equality of two key instances. */ +@Stable sealed class Key(val debugName: String, val identity: Any) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index f5b271019748..f5561cb404b6 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -19,11 +19,13 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -34,6 +36,7 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.zIndex /** A scene in a [SceneTransitionLayout]. */ +@Stable internal class Scene( val key: SceneKey, layoutImpl: SceneTransitionLayoutImpl, @@ -105,11 +108,13 @@ private class SceneScopeImpl( ): State<T> { val element = element?.let { key -> - layoutImpl.elements[key] - ?: error( - "Element $key is not composed. Make sure to call animateSharedXAsState " + - "*after* Modifier.element(key)." - ) + Snapshot.withoutReadObservation { + layoutImpl.elements[key] + ?: error( + "Element $key is not composed. Make sure to call " + + "animateSharedXAsState *after* Modifier.element(key)." + ) + } } return animateSharedValueAsState( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 1f698ff00141..07add77eccd4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -19,6 +19,7 @@ package com.android.compose.animation.scene import androidx.annotation.FloatRange import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -95,6 +96,7 @@ interface SceneTransitionLayoutScope { @DslMarker annotation class ElementDsl @ElementDsl +@Stable interface SceneScope { /** The state of the [SceneTransitionLayout] in which this scene is contained. */ val layoutState: SceneTransitionLayoutState diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 60f385aede02..02ddccbc051b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf @@ -41,6 +42,7 @@ import com.android.compose.ui.util.lerp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel +@Stable internal class SceneTransitionLayoutImpl( onChangeScene: (SceneKey) -> Unit, builder: SceneTransitionLayoutScope.() -> Unit, 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 6a87662135de..f48e9147eef4 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 @@ -16,11 +16,13 @@ package com.android.compose.animation.scene +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue /** The state of a [SceneTransitionLayout]. */ +@Stable class SceneTransitionLayoutState(initialScene: SceneKey) { /** * The current [TransitionState]. All values read here are backed by the Snapshot system. @@ -29,7 +31,6 @@ class SceneTransitionLayoutState(initialScene: SceneKey) { * [SceneTransitionLayoutState.observableTransitionState] instead. */ var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene)) - internal set /** * Whether we are transitioning, optionally restricting the check to the transition between @@ -54,6 +55,7 @@ class SceneTransitionLayoutState(initialScene: SceneKey) { } } +@Stable sealed interface TransitionState { /** * The current effective scene. If a new transition was triggered, it would start from this 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 828dff27637b..f91895bb0e05 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 @@ -18,6 +18,7 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.snap +import androidx.compose.runtime.Stable import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastForEach @@ -92,6 +93,7 @@ class SceneTransitions( } /** The definition of a transition between [from] and [to]. */ +@Stable data class TransitionSpec( val from: SceneKey?, val to: SceneKey?, 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 cc7a0b8e33b2..ce3e1db2c3d0 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 @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -427,4 +428,30 @@ class ElementTest { assertThat(barElement.sceneValues.keys).containsExactly(TestScenes.SceneA) assertThat(fooElement.sceneValues).isEmpty() } + + @Test + fun existingElementsDontRecomposeWhenTransitionStateChanges() { + var fooCompositions = 0 + + rule.testTransition( + fromSceneContent = { + SideEffect { fooCompositions++ } + Box(Modifier.element(TestElements.Foo)) + }, + toSceneContent = {}, + transition = { + spec = tween(4 * 16) + + scaleSize(TestElements.Foo, width = 2f, height = 0.5f) + translate(TestElements.Foo, x = 10.dp, y = 10.dp) + fade(TestElements.Foo) + } + ) { + before { assertThat(fooCompositions).isEqualTo(1) } + at(16) { assertThat(fooCompositions).isEqualTo(1) } + at(32) { assertThat(fooCompositions).isEqualTo(1) } + at(48) { assertThat(fooCompositions).isEqualTo(1) } + after { assertThat(fooCompositions).isEqualTo(1) } + } + } } |