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

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

Merge "Animate overlays when enabling/disabling them" into main

parents f0c0bfce 8d0450bb
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -26,15 +26,15 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

internal fun CoroutineScope.animateContent(
    layoutState: MutableSceneTransitionLayoutStateImpl,
    transition: TransitionState.Transition,
    oneOffAnimation: OneOffAnimation,
    targetProgress: Float,
    startTransition: () -> Unit,
    finishTransition: () -> Unit,
    chain: Boolean = true,
) {
    // Start the transition. This will compute the TransformationSpec associated to [transition],
    // which we need to initialize the Animatable that will actually animate it.
    startTransition()
    layoutState.startTransition(transition, chain)

    // The transition now contains the transformation spec that we should use to instantiate the
    // Animatable.
@@ -59,7 +59,7 @@ internal fun CoroutineScope.animateContent(
            try {
                animatable.animateTo(targetProgress, animationSpec, initialVelocity)
            } finally {
                finishTransition()
                layoutState.finishTransition(transition)
            }
        }
}
+144 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job

/** Trigger a one-off transition to show or hide an overlay. */
internal fun CoroutineScope.showOrHideOverlay(
    layoutState: MutableSceneTransitionLayoutStateImpl,
    overlay: OverlayKey,
    fromOrToScene: SceneKey,
    isShowing: Boolean,
    transitionKey: TransitionKey?,
    replacedTransition: TransitionState.Transition.ShowOrHideOverlay?,
    reversed: Boolean,
): TransitionState.Transition.ShowOrHideOverlay {
    val targetProgress = if (reversed) 0f else 1f
    val (fromContent, toContent) =
        if (isShowing xor reversed) {
            fromOrToScene to overlay
        } else {
            overlay to fromOrToScene
        }

    val oneOffAnimation = OneOffAnimation()
    val transition =
        OneOffShowOrHideOverlayTransition(
            overlay = overlay,
            fromOrToScene = fromOrToScene,
            fromContent = fromContent,
            toContent = toContent,
            isEffectivelyShown = isShowing,
            key = transitionKey,
            replacedTransition = replacedTransition,
            oneOffAnimation = oneOffAnimation,
        )

    animateContent(
        layoutState = layoutState,
        transition = transition,
        oneOffAnimation = oneOffAnimation,
        targetProgress = targetProgress,
    )

    return transition
}

/** Trigger a one-off transition to replace an overlay by another one. */
internal fun CoroutineScope.replaceOverlay(
    layoutState: MutableSceneTransitionLayoutStateImpl,
    fromOverlay: OverlayKey,
    toOverlay: OverlayKey,
    transitionKey: TransitionKey?,
    replacedTransition: TransitionState.Transition.ReplaceOverlay?,
    reversed: Boolean,
): TransitionState.Transition.ReplaceOverlay {
    val targetProgress = if (reversed) 0f else 1f
    val effectivelyShownOverlay = if (reversed) fromOverlay else toOverlay

    val oneOffAnimation = OneOffAnimation()
    val transition =
        OneOffOverlayReplacingTransition(
            fromOverlay = fromOverlay,
            toOverlay = toOverlay,
            effectivelyShownOverlay = effectivelyShownOverlay,
            key = transitionKey,
            replacedTransition = replacedTransition,
            oneOffAnimation = oneOffAnimation,
        )

    animateContent(
        layoutState = layoutState,
        transition = transition,
        oneOffAnimation = oneOffAnimation,
        targetProgress = targetProgress,
    )

    return transition
}

private class OneOffShowOrHideOverlayTransition(
    overlay: OverlayKey,
    fromOrToScene: SceneKey,
    fromContent: ContentKey,
    toContent: ContentKey,
    override val isEffectivelyShown: Boolean,
    override val key: TransitionKey?,
    replacedTransition: TransitionState.Transition?,
    private val oneOffAnimation: OneOffAnimation,
) :
    TransitionState.Transition.ShowOrHideOverlay(
        overlay,
        fromOrToScene,
        fromContent,
        toContent,
        replacedTransition,
    ) {
    override val progress: Float
        get() = oneOffAnimation.progress

    override val progressVelocity: Float
        get() = oneOffAnimation.progressVelocity

    override val isInitiatedByUserInput: Boolean = false
    override val isUserInputOngoing: Boolean = false

    override fun finish(): Job = oneOffAnimation.finish()
}

private class OneOffOverlayReplacingTransition(
    fromOverlay: OverlayKey,
    toOverlay: OverlayKey,
    override val effectivelyShownOverlay: OverlayKey,
    override val key: TransitionKey?,
    replacedTransition: TransitionState.Transition?,
    private val oneOffAnimation: OneOffAnimation,
) : TransitionState.Transition.ReplaceOverlay(fromOverlay, toOverlay, replacedTransition) {
    override val progress: Float
        get() = oneOffAnimation.progress

    override val progressVelocity: Float
        get() = oneOffAnimation.progressVelocity

    override val isInitiatedByUserInput: Boolean = false
    override val isUserInputOngoing: Boolean = false

    override fun finish(): Job = oneOffAnimation.finish()
}
+1 −1
Original line number Diff line number Diff line
@@ -412,7 +412,7 @@ private class AnimatedStateImpl<T, Delta>(
                            if (canOverflow) transition.progress
                            else transition.progress.fastCoerceIn(0f, 1f)
                        }
                        overscrollSpec.scene == transition.toContent -> 1f
                        overscrollSpec.content == transition.toContent -> 1f
                        else -> 0f
                    }

+2 −2
Original line number Diff line number Diff line
@@ -166,11 +166,11 @@ private fun CoroutineScope.animateToScene(
        }

    animateContent(
        layoutState = layoutState,
        transition = transition,
        oneOffAnimation = oneOffAnimation,
        targetProgress = targetProgress,
        startTransition = { layoutState.startTransition(transition, chain) },
        finishTransition = { layoutState.finishTransition(transition) },
        chain = chain,
    )

    return transition
+7 −6
Original line number Diff line number Diff line
@@ -310,9 +310,10 @@ internal class ElementNode(

        val transition = elementState as? TransitionState.Transition

        // If this element is not supposed to be laid out now because the other content of its
        // transition is overscrolling, then lay out the element normally and don't place it.
        val overscrollScene = transition?.currentOverscrollSpec?.scene
        // If this element is not supposed to be laid out now, either because it is not part of any
        // ongoing transition or the other content of its transition is overscrolling, then lay out
        // the element normally and don't place it.
        val overscrollScene = transition?.currentOverscrollSpec?.content
        val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != content.key
        if (isOtherSceneOverscrolling) {
            return doNotPlace(measurable, constraints)
@@ -845,7 +846,7 @@ internal fun shouldPlaceOrComposeSharedElement(
    transition: TransitionState.Transition,
): Boolean {
    // If we are overscrolling, only place/compose the element in the overscrolling scene.
    val overscrollScene = transition.currentOverscrollSpec?.scene
    val overscrollScene = transition.currentOverscrollSpec?.content
    if (overscrollScene != null) {
        return content == overscrollScene
    }
@@ -1184,7 +1185,7 @@ private inline fun <T> computeValue(
    val currentContent = currentContentState.content
    if (transition is TransitionState.HasOverscrollProperties) {
        val overscroll = transition.currentOverscrollSpec
        if (overscroll?.scene == currentContent) {
        if (overscroll?.content == currentContent) {
            val elementSpec =
                overscroll.transformationSpec.transformations(element.key, currentContent)
            val propertySpec = transformation(elementSpec) ?: return currentValue()
@@ -1210,7 +1211,7 @@ private inline fun <T> computeValue(
            // TODO(b/290184746): Make sure that we don't overflow transformations associated to a
            // range.
            val directionSign = if (transition.isUpOrLeft) -1 else 1
            val isToContent = overscroll.scene == transition.toContent
            val isToContent = overscroll.content == transition.toContent
            val linearProgress = transition.progress.let { if (isToContent) it - 1f else it }
            val progressConverter =
                overscroll.progressConverter
Loading