Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 217978f0 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Introduce SharedElementScenePicker" into main

parents cff22570 a5370f97
Loading
Loading
Loading
Loading
+43 −19
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
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.util.lerp

@@ -196,29 +197,44 @@ private fun shouldDrawElement(
            state.fromScene == state.toScene ||
            !layoutImpl.isTransitionReady(state) ||
            state.fromScene !in element.sceneValues ||
            state.toScene !in element.sceneValues ||
            !isSharedElementEnabled(layoutImpl, state, element.key)
            state.toScene !in element.sceneValues
    ) {
        return true
    }

    val otherScene =
        layoutImpl.scenes.getValue(
            if (scene.key == state.fromScene) {
                state.toScene
            } else {
                state.fromScene
    val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key)
    if (sharedTransformation?.enabled == false) {
        return true
    }
        )

    // When the element is shared, draw the one in the highest scene unless it is a background, i.e.
    // it is usually drawn below everything else.
    val isHighestScene = scene.zIndex > otherScene.zIndex
    return if (element.key.isBackground) {
        !isHighestScene
    } else {
        isHighestScene
    return shouldDrawOrComposeSharedElement(
        layoutImpl,
        state,
        scene.key,
        element.key,
        sharedTransformation,
    )
}

internal fun shouldDrawOrComposeSharedElement(
    layoutImpl: SceneTransitionLayoutImpl,
    transition: TransitionState.Transition,
    scene: SceneKey,
    element: ElementKey,
    sharedTransformation: SharedElementTransformation?
): Boolean {
    val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker
    val fromScene = transition.fromScene
    val toScene = transition.toScene

    return scenePicker.sceneDuringTransition(
        element = element,
        fromScene = fromScene,
        toScene = toScene,
        progress = transition::progress,
        fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
        toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
    ) == scene
}

private fun isSharedElementEnabled(
@@ -226,6 +242,14 @@ private fun isSharedElementEnabled(
    transition: TransitionState.Transition,
    element: ElementKey,
): Boolean {
    return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true
}

internal fun sharedElementTransformation(
    layoutImpl: SceneTransitionLayoutImpl,
    transition: TransitionState.Transition,
    element: ElementKey,
): SharedElementTransformation? {
    val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
    val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
    val sharedInToScene = spec.transformations(element, transition.toScene).shared
@@ -238,7 +262,7 @@ private fun isSharedElementEnabled(
        )
    }

    return sharedInFromScene?.enabled ?: true
    return sharedInFromScene
}

/**
+19 −9
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.Modifier
@@ -60,7 +62,16 @@ internal fun MovableElement(
        // which case we still need to draw it.
        val picture = remember { Picture() }

        if (shouldComposeMovableElement(layoutImpl, scene.key, element)) {
        // Whether we should compose the movable element here. The scene picker logic to know in
        // which scene we should compose/draw a movable element might depend on the current
        // transition progress, so we put this in a derivedStateOf to prevent many recompositions
        // during the transition.
        val shouldComposeMovableElement by
            remember(layoutImpl, scene.key, element) {
                derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) }
            }

        if (shouldComposeMovableElement) {
            Box(
                Modifier.drawWithCache {
                    val width = size.width.toInt()
@@ -172,14 +183,13 @@ private fun shouldComposeMovableElement(
        return scene == fromScene
    }

    // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless
    // it is a background) given that this is the one that is going to be drawn.
    val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex
    return if (element.key.isBackground) {
        !isHighestScene
    } else {
        isHighestScene
    }
    return shouldDrawOrComposeSharedElement(
        layoutImpl,
        transitionState,
        scene,
        element.key,
        sharedElementTransformation(layoutImpl, transitionState, element.key),
    )
}

private class MovableElementScopeImpl(
+45 −1
Original line number Diff line number Diff line
@@ -120,8 +120,14 @@ interface TransitionBuilder : PropertyTransformationBuilder {
     *
     * @param enabled whether the matched element(s) should actually be shared in this transition.
     *   Defaults to true.
     * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we
     *   should draw or compose this shared element.
     */
    fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
    fun sharedElement(
        matcher: ElementMatcher,
        enabled: Boolean = true,
        scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker,
    )

    /**
     * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
@@ -144,6 +150,44 @@ interface TransitionBuilder : PropertyTransformationBuilder {
    fun reversed(builder: TransitionBuilder.() -> Unit)
}

interface SharedElementScenePicker {
    /**
     * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
     * composed (when using `MovableElement(key)`) during the transition from [fromScene] to
     * [toScene].
     */
    fun sceneDuringTransition(
        element: ElementKey,
        fromScene: SceneKey,
        toScene: SceneKey,
        progress: () -> Float,
        fromSceneZIndex: Float,
        toSceneZIndex: Float,
    ): SceneKey
}

object DefaultSharedElementScenePicker : SharedElementScenePicker {
    override fun sceneDuringTransition(
        element: ElementKey,
        fromScene: SceneKey,
        toScene: SceneKey,
        progress: () -> Float,
        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)
        ) {
            fromScene
        } else {
            toScene
        }
    }
}

@TransitionDsl
interface PropertyTransformationBuilder {
    /**
+6 −2
Original line number Diff line number Diff line
@@ -111,8 +111,12 @@ internal class TransitionBuilderImpl : TransitionBuilder {
        range = null
    }

    override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
        transformations.add(SharedElementTransformation(matcher, enabled))
    override fun sharedElement(
        matcher: ElementMatcher,
        enabled: Boolean,
        scenePicker: SharedElementScenePicker,
    ) {
        transformations.add(SharedElementTransformation(matcher, enabled, scenePicker))
    }

    override fun timestampRange(
+2 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.SharedElementScenePicker
import com.android.compose.animation.scene.TransitionState

/** A transformation applied to one or more elements during a transition. */
@@ -48,6 +49,7 @@ sealed interface Transformation {
internal class SharedElementTransformation(
    override val matcher: ElementMatcher,
    internal val enabled: Boolean,
    internal val scenePicker: SharedElementScenePicker,
) : Transformation

/** A transformation that is applied on the element during the whole transition. */
Loading