Remove ElementKey.isBackground (1/2)
This CL is a small refactor that completely removes the
ElementKey.isBackground modifier, and instead introduces a
LowestZIndexScenePicker that can be used to draw background in the scene
with lowest zIndex.
Bug: 317026105
Test: ElementScenePickerTest
Flag: N/A
Change-Id: I61cbc6010f50d839cf7046a97e17afcecc48556c
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 ae28d0a..280fbfb 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
@@ -291,7 +291,7 @@
scene: SceneKey,
element: ElementKey,
): Boolean {
- val scenePicker = element.scenePicker ?: DefaultElementScenePicker
+ val scenePicker = element.scenePicker
val fromScene = transition.fromScene
val toScene = transition.toScene
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 dc1ddb6..90f46bd 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
@@ -64,16 +64,10 @@
identity: Any = Object(),
/**
- * Whether this element is a background and usually drawn below other elements. This should be
- * set to true to make sure that shared backgrounds are drawn below elements of other scenes.
+ * The [ElementScenePicker] to use when deciding in which scene we should draw shared Elements
+ * or compose MovableElements.
*/
- val isBackground: Boolean = false,
-
- /**
- * The [ElementScenePicker] to use when deciding in which scene we should draw or compose this
- * element when it is shared.
- */
- val scenePicker: ElementScenePicker? = null,
+ val scenePicker: ElementScenePicker = DefaultElementScenePicker,
) : Key(name, identity), ElementMatcher {
@VisibleForTesting
// TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
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 1ae21cc..f6e2d79 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
@@ -130,10 +130,22 @@
fun reversed(builder: TransitionBuilder.() -> Unit)
}
+/**
+ * An interface to decide where we should draw shared Elements or compose MovableElements.
+ *
+ * @see DefaultElementScenePicker
+ * @see HighestZIndexScenePicker
+ * @see LowestZIndexScenePicker
+ */
interface ElementScenePicker {
/**
* Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
* composed (when using `MovableElement(key)`) during the given [transition].
+ *
+ * Important: For [MovableElements][SceneScope.MovableElement], this scene picker will *always*
+ * be used during transitions to decide whether we should compose that element in a given scene
+ * or not. Therefore, you should make sure that the returned [SceneKey] contains the movable
+ * element, otherwise that element will not be composed in any scene during the transition.
*/
fun sceneDuringTransition(
element: ElementKey,
@@ -183,19 +195,15 @@
}
}
-object DefaultElementScenePicker : ElementScenePicker {
+/** An [ElementScenePicker] that draws/composes elements in the scene with the highest z-order. */
+object HighestZIndexScenePicker : ElementScenePicker {
override fun sceneDuringTransition(
element: ElementKey,
transition: TransitionState.Transition,
fromSceneZIndex: Float,
toSceneZIndex: Float
): SceneKey {
- // By default shared elements are drawn in the highest scene possible, unless it is a
- // background.
- return if (
- (fromSceneZIndex > toSceneZIndex && !element.isBackground) ||
- (fromSceneZIndex < toSceneZIndex && element.isBackground)
- ) {
+ return if (fromSceneZIndex > toSceneZIndex) {
transition.fromScene
} else {
transition.toScene
@@ -203,6 +211,25 @@
}
}
+/** An [ElementScenePicker] that draws/composes elements in the scene with the lowest z-order. */
+object LowestZIndexScenePicker : ElementScenePicker {
+ override fun sceneDuringTransition(
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ fromSceneZIndex: Float,
+ toSceneZIndex: Float
+ ): SceneKey {
+ return if (fromSceneZIndex < toSceneZIndex) {
+ transition.fromScene
+ } else {
+ transition.toScene
+ }
+ }
+}
+
+/** The default [ElementScenePicker]. */
+val DefaultElementScenePicker = HighestZIndexScenePicker
+
@TransitionDsl
interface PropertyTransformationBuilder {
/**
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt
new file mode 100644
index 0000000..3b022e8
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ElementScenePickerTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun highestZIndexPicker() {
+ val key = ElementKey("TestElement", scenePicker = HighestZIndexScenePicker)
+ rule.testTransition(
+ fromSceneContent = { Box(Modifier.element(key).size(10.dp)) },
+ toSceneContent = { Box(Modifier.element(key).size(10.dp)) },
+ transition = { spec = tween(4 * 16, easing = LinearEasing) },
+ fromScene = TestScenes.SceneA,
+ toScene = TestScenes.SceneB,
+ ) {
+ before {
+ onElement(key, TestScenes.SceneA).assertIsDisplayed()
+ onElement(key, TestScenes.SceneB).assertDoesNotExist()
+ }
+ at(32) {
+ // Scene B has the highest index, so the element is placed only there.
+ onElement(key, TestScenes.SceneA).assertExists().assertIsNotDisplayed()
+ onElement(key, TestScenes.SceneB).assertIsDisplayed()
+ }
+ after {
+ onElement(key, TestScenes.SceneA).assertDoesNotExist()
+ onElement(key, TestScenes.SceneB).assertIsDisplayed()
+ }
+ }
+ }
+
+ @Test
+ fun lowestZIndexPicker() {
+ val key = ElementKey("TestElement", scenePicker = LowestZIndexScenePicker)
+ rule.testTransition(
+ fromSceneContent = { Box(Modifier.element(key).size(10.dp)) },
+ toSceneContent = { Box(Modifier.element(key).size(10.dp)) },
+ transition = { spec = tween(4 * 16, easing = LinearEasing) },
+ fromScene = TestScenes.SceneA,
+ toScene = TestScenes.SceneB,
+ ) {
+ before {
+ onElement(key, TestScenes.SceneA).assertIsDisplayed()
+ onElement(key, TestScenes.SceneB).assertDoesNotExist()
+ }
+ at(32) {
+ // Scene A has the lowest index, so the element is placed only there.
+ onElement(key, TestScenes.SceneA).assertIsDisplayed()
+ onElement(key, TestScenes.SceneB).assertExists().assertIsNotDisplayed()
+ }
+ after {
+ onElement(key, TestScenes.SceneA).assertDoesNotExist()
+ onElement(key, TestScenes.SceneB).assertIsDisplayed()
+ }
+ }
+ }
+}