diff options
| -rw-r--r-- | packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt (renamed from packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt) | 0 | ||||
| -rw-r--r-- | packages/SystemUI/compose/scene/Android.bp | 2 | ||||
| -rw-r--r-- | packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt | 53 | ||||
| -rw-r--r-- | packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt | 47 | ||||
| -rw-r--r-- | packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt | 11 | ||||
| -rw-r--r-- | packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt | 18 | ||||
| -rw-r--r-- | packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt | 7 | ||||
| -rw-r--r-- | packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt | 1 |
8 files changed, 135 insertions, 4 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt index f49939ba3a2d..f49939ba3a2d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp index af1172bddfc8..682c49cfd19c 100644 --- a/packages/SystemUI/compose/scene/Android.bp +++ b/packages/SystemUI/compose/scene/Android.bp @@ -40,6 +40,8 @@ android_library { static_libs: [ "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", + + "PlatformComposeCore", ], kotlincflags: ["-Xjvm-default=all"], 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 ebe1df4bf55f..f14622fe7b65 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 @@ -48,10 +48,13 @@ import androidx.compose.ui.unit.round import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastForEachReversed import androidx.compose.ui.util.lerp +import com.android.compose.animation.scene.Element.State import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation +import com.android.compose.modifiers.thenIf +import com.android.compose.ui.graphics.drawInContainer import com.android.compose.ui.util.lerp import kotlin.math.roundToInt import kotlinx.coroutines.launch @@ -146,10 +149,58 @@ internal fun Modifier.element( // TODO(b/341072461): Revert this and read the current transitions in ElementNode directly once // we can ensure that SceneTransitionLayoutImpl will compose new contents first. val currentTransitionStates = layoutImpl.state.transitionStates - return then(ElementModifier(layoutImpl, currentTransitionStates, content, key)) + return thenIf(layoutImpl.state.isElevationPossible(content.key, key)) { + Modifier.maybeElevateInContent(layoutImpl, content, key, currentTransitionStates) + } + .then(ElementModifier(layoutImpl, currentTransitionStates, content, key)) .testTag(key.testTag) } +private fun Modifier.maybeElevateInContent( + layoutImpl: SceneTransitionLayoutImpl, + content: Content, + key: ElementKey, + transitionStates: List<TransitionState>, +): Modifier { + fun isSharedElement( + stateByContent: Map<ContentKey, State>, + transition: TransitionState.Transition, + ): Boolean { + fun inFromContent() = transition.fromContent in stateByContent + fun inToContent() = transition.toContent in stateByContent + fun inCurrentScene() = transition.currentScene in stateByContent + + return if (transition is TransitionState.Transition.ReplaceOverlay) { + (inFromContent() && (inToContent() || inCurrentScene())) || + (inToContent() && inCurrentScene()) + } else { + inFromContent() && inToContent() + } + } + + return drawInContainer( + content.containerState, + enabled = { + val stateByContent = layoutImpl.elements.getValue(key).stateByContent + val state = elementState(transitionStates, isInContent = { it in stateByContent }) + + state is TransitionState.Transition && + state.transformationSpec + .transformations(key, content.key) + .shared + ?.elevateInContent == content.key && + isSharedElement(stateByContent, state) && + isSharedElementEnabled(key, state) && + shouldPlaceElement( + layoutImpl, + content.key, + layoutImpl.elements.getValue(key), + state, + ) + }, + ) +} + /** * An element associated to [ElementNode]. Note that this element does not support updates as its * arguments should always be the same. 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 a9a8668bd304..e1e2411da080 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 @@ -19,13 +19,16 @@ package com.android.compose.animation.scene import android.util.Log import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.util.fastAll +import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastFilter import androidx.compose.ui.util.fastForEach import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transition.link.LinkedTransition import com.android.compose.animation.scene.transition.link.StateLink import kotlin.math.absoluteValue @@ -271,6 +274,14 @@ internal class MutableSceneTransitionLayoutStateImpl( mutableStateOf(listOf(TransitionState.Idle(initialScene, initialOverlays))) private set + /** + * The flattened list of [SharedElementTransformation] within all the transitions in + * [transitionStates]. + */ + private val transformationsWithElevation: List<SharedElementTransformation> by derivedStateOf { + transformationsWithElevation(transitionStates) + } + override val currentScene: SceneKey get() = transitionState.currentScene @@ -743,6 +754,42 @@ internal class MutableSceneTransitionLayoutStateImpl( animate() } + + private fun transformationsWithElevation( + transitionStates: List<TransitionState> + ): List<SharedElementTransformation> { + return buildList { + transitionStates.fastForEach { state -> + if (state !is TransitionState.Transition) { + return@fastForEach + } + + state.transformationSpec.transformations.fastForEach { transformation -> + if ( + transformation is SharedElementTransformation && + transformation.elevateInContent != null + ) { + add(transformation) + } + } + } + } + } + + /** + * Return whether we might need to elevate [element] (or any element if [element] is `null`) in + * [content]. + * + * This is used to compose `Modifier.container()` and `Modifier.drawInContainer()` only when + * necessary, for performance. + */ + internal fun isElevationPossible(content: ContentKey, element: ElementKey?): Boolean { + if (transformationsWithElevation.isEmpty()) return false + return transformationsWithElevation.fastAny { transformation -> + transformation.elevateInContent == content && + (element == null || transformation.matcher.matches(element, content)) + } + } } private const val TAG = "SceneTransitionLayoutState" 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 e825c6e271ed..dc26b6b382b4 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 @@ -204,8 +204,17 @@ interface TransitionBuilder : BaseTransitionBuilder { * * @param enabled whether the matched element(s) should actually be shared in this transition. * Defaults to true. + * @param elevateInContent the content in which we should elevate the element when it is shared, + * drawing above all other composables of that content. If `null` (the default), we will + * simply draw this element in its original location. If not `null`, it has to be either the + * [fromContent][TransitionState.Transition.fromContent] or + * [toContent][TransitionState.Transition.toContent] of the transition. */ - fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true) + fun sharedElement( + matcher: ElementMatcher, + enabled: Boolean = true, + elevateInContent: ContentKey? = null, + ) /** * Adds the transformations in [builder] but in reversed order. This allows you to partially diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index a5ad999f7a64..269d91b02e7d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -249,8 +249,22 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr reversed = false } - override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) { - transformations.add(SharedElementTransformation(matcher, enabled)) + override fun sharedElement( + matcher: ElementMatcher, + enabled: Boolean, + elevateInContent: ContentKey?, + ) { + check( + elevateInContent == null || + elevateInContent == transition.fromContent || + elevateInContent == transition.toContent + ) { + "elevateInContent (${elevateInContent?.debugName}) should be either fromContent " + + "(${transition.fromContent.debugName}) or toContent " + + "(${transition.toContent.debugName})" + } + + transformations.add(SharedElementTransformation(matcher, enabled, elevateInContent)) } override fun timestampRange( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index c8407b13db66..8187e3932975 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -51,6 +51,9 @@ import com.android.compose.animation.scene.animateSharedValueAsState import com.android.compose.animation.scene.element import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions import com.android.compose.animation.scene.nestedScrollToScene +import com.android.compose.modifiers.thenIf +import com.android.compose.ui.graphics.ContainerState +import com.android.compose.ui.graphics.container /** A content defined in a [SceneTransitionLayout], i.e. a scene or an overlay. */ @Stable @@ -62,6 +65,7 @@ internal sealed class Content( zIndex: Float, ) { internal val scope = ContentScopeImpl(layoutImpl, content = this) + val containerState = ContainerState() var content by mutableStateOf(content) var zIndex by mutableFloatStateOf(zIndex) @@ -82,6 +86,9 @@ internal sealed class Content( val placeable = measurable.measure(constraints) layout(placeable.width, placeable.height) { placeable.place(0, 0) } } + .thenIf(layoutImpl.state.isElevationPossible(content = key, element = null)) { + Modifier.container(containerState) + } .testTag(key.testTag) ) { scope.content() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index 9bb302307359..de7f418f219a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -52,6 +52,7 @@ sealed interface Transformation { internal class SharedElementTransformation( override val matcher: ElementMatcher, internal val enabled: Boolean, + internal val elevateInContent: ContentKey?, ) : Transformation /** A transformation that changes the value of an element property, like its size or offset. */ |