diff options
4 files changed, 114 insertions, 0 deletions
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 6a7a3a00d4fe..30e50a972230 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 @@ -34,6 +34,7 @@ import androidx.compose.ui.layout.intermediateLayout import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.IntSize import androidx.compose.ui.zIndex +import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions /** A scene in a [SceneTransitionLayout]. */ @Stable @@ -152,4 +153,8 @@ private class SceneScopeImpl( bounds: ElementKey, shape: Shape ): Modifier = punchHole(layoutImpl, element, bounds, shape) + + override fun Modifier.noResizeDuringTransitions(): Modifier { + return noResizeDuringTransitions(layoutState = layoutImpl.state) + } } 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 3608e374fdbc..d455aa2ed5a0 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 @@ -205,6 +205,12 @@ interface SceneScope { * the result. */ fun Modifier.punchHole(element: ElementKey, bounds: ElementKey, shape: Shape): Modifier + + /** + * Don't resize during transitions. This can for instance be used to make sure that scrollable + * lists keep a constant size during transitions even if its elements are growing/shrinking. + */ + fun Modifier.noResizeDuringTransitions(): Modifier } // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt new file mode 100644 index 000000000000..bd36cb8655ac --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt @@ -0,0 +1,40 @@ +/* + * 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.modifiers + +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.intermediateLayout +import androidx.compose.ui.unit.Constraints +import com.android.compose.animation.scene.SceneTransitionLayoutState + +@OptIn(ExperimentalComposeUiApi::class) +internal fun Modifier.noResizeDuringTransitions(layoutState: SceneTransitionLayoutState): Modifier { + return intermediateLayout { measurable, constraints -> + if (layoutState.currentTransition == null) { + return@intermediateLayout measurable.measure(constraints).run { + layout(width, height) { place(0, 0) } + } + } + + // Make sure that this layout node has the same size than when we are at rest. + val sizeAtRest = lookaheadSize + measurable.measure(Constraints.fixed(sizeAtRest.width, sizeAtRest.height)).run { + layout(width, height) { place(0, 0) } + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt new file mode 100644 index 000000000000..2c159d15fa66 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt @@ -0,0 +1,63 @@ +/* + * 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.modifiers + +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.platform.testTag +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestElements +import com.android.compose.animation.scene.element +import com.android.compose.animation.scene.testTransition +import com.android.compose.test.assertSizeIsEqualTo +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SizeTest { + @get:Rule val rule = createComposeRule() + + @Test + fun noResizeDuringTransitions() { + // The tag for the parent of the shared Foo element. + val parentTag = "parent" + + rule.testTransition( + fromSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) }, + toSceneContent = { + // Don't resize the parent of Foo during transitions so that it's always the same + // size as when there is no transition (200dp). + Box(Modifier.noResizeDuringTransitions().testTag(parentTag)) { + Box(Modifier.element(TestElements.Foo).size(200.dp)) + } + }, + transition = { spec = tween(durationMillis = 4 * 16, easing = LinearEasing) }, + ) { + at(16) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) } + at(32) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) } + at(48) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) } + after { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) } + } + } +} |