Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +6 −5 Original line number Diff line number Diff line Loading @@ -174,8 +174,8 @@ private fun <T> computeValue( lerp: (T, T, Float) -> T, canOverflow: Boolean, ): T { val state = layoutImpl.state.transitionState if (state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state)) { val transition = layoutImpl.state.currentTransition if (transition == null || !layoutImpl.isTransitionReady(transition)) { return sharedValue.value } Loading @@ -191,10 +191,11 @@ private fun <T> computeValue( return value as Element.SharedValue<T> } val fromValue = sceneValue(state.fromScene) val toValue = sceneValue(state.toScene) val fromValue = sceneValue(transition.fromScene) val toValue = sceneValue(transition.toScene) return if (fromValue != null && toValue != null) { val progress = if (canOverflow) state.progress else state.progress.coerceIn(0f, 1f) val progress = if (canOverflow) transition.progress else transition.progress.coerceIn(0f, 1f) lerp(fromValue.value, toValue.value, progress) } else if (fromValue != null) { fromValue.value Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +47 −36 Original line number Diff line number Diff line Loading @@ -28,11 +28,11 @@ import kotlinx.coroutines.launch * the currently running transition, if there is one. */ internal fun CoroutineScope.animateToScene( layoutImpl: SceneTransitionLayoutImpl, layoutState: SceneTransitionLayoutStateImpl, target: SceneKey, ) { val state = layoutImpl.state.transitionState if (state.currentScene == target) { val transitionState = layoutState.transitionState if (transitionState.currentScene == target) { // This can happen in 3 different situations, for which there isn't anything else to do: // 1. There is no ongoing transition and [target] is already the current scene. // 2. The user is swiping to [target] from another scene and released their pointer such Loading @@ -44,44 +44,47 @@ internal fun CoroutineScope.animateToScene( return } when (state) { is TransitionState.Idle -> animate(layoutImpl, target) when (transitionState) { is TransitionState.Idle -> animate(layoutState, target) is TransitionState.Transition -> { // A transition is currently running: first check whether `transition.toScene` or // `transition.fromScene` is the same as our target scene, in which case the transition // can be accelerated or reversed to end up in the target state. if (state.toScene == target) { if (transitionState.toScene == target) { // The user is currently swiping to [target] but didn't release their pointer yet: // animate the progress to `1`. check(state.fromScene == state.currentScene) val progress = state.progress check(transitionState.fromScene == transitionState.currentScene) val progress = transitionState.progress if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) { // The transition is already finished (progress ~= 1): no need to animate. layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene) // The transition is already finished (progress ~= 1): no need to animate. We // finish the current transition early to make sure that the current state // change is committed. layoutState.finishTransition(transitionState, transitionState.currentScene) } else { // The transition is in progress: start the canned animation at the same // progress as it was in. // TODO(b/290184746): Also take the current velocity into account. animate(layoutImpl, target, startProgress = progress) animate(layoutState, target, startProgress = progress) } return } if (state.fromScene == target) { if (transitionState.fromScene == target) { // There is a transition from [target] to another scene: simply animate the same // transition progress to `0`. check(state.toScene == state.currentScene) val progress = state.progress check(transitionState.toScene == transitionState.currentScene) val progress = transitionState.progress if (progress.absoluteValue < ProgressVisibilityThreshold) { // The transition is at progress ~= 0: no need to animate. layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene) // The transition is at progress ~= 0: no need to animate.We finish the current // transition early to make sure that the current state change is committed. layoutState.finishTransition(transitionState, transitionState.currentScene) } else { // TODO(b/290184746): Also take the current velocity into account. animate(layoutImpl, target, startProgress = progress, reversed = true) animate(layoutState, target, startProgress = progress, reversed = true) } return Loading @@ -89,27 +92,22 @@ internal fun CoroutineScope.animateToScene( // Generic interruption; the current transition is neither from or to [target]. // TODO(b/290930950): Better handle interruptions here. animate(layoutImpl, target) animate(layoutState, target) } } } private fun CoroutineScope.animate( layoutImpl: SceneTransitionLayoutImpl, layoutState: SceneTransitionLayoutStateImpl, target: SceneKey, startProgress: Float = 0f, reversed: Boolean = false, ) { val fromScene = layoutImpl.state.transitionState.currentScene val fromScene = layoutState.transitionState.currentScene val isUserInput = (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput (layoutState.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput ?: false val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec val visibilityThreshold = (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold) val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { Loading @@ -119,7 +117,6 @@ private fun CoroutineScope.animate( currentScene = target, isInitiatedByUserInput = isUserInput, isUserInputOngoing = false, animatable = animatable, ) } else { OneOffTransition( Loading @@ -128,21 +125,27 @@ private fun CoroutineScope.animate( currentScene = target, isInitiatedByUserInput = isUserInput, isUserInputOngoing = false, animatable = animatable, ) } // Change the current layout state to use this new transition. layoutImpl.state.transitionState = transition // Change the current layout state to start this new transition. This will compute the // TransformationSpec associated to this transition, which we need to initialize the Animatable // that will actually animate it. layoutState.startTransition(transition) // The transformation now contains the spec that we should use to instantiate the Animatable. val animationSpec = layoutState.transformationSpec.progressSpec val visibilityThreshold = (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold).also { transition.animatable = it } // Animate the progress to its target value. launch { animatable.animateTo(targetProgress, animationSpec) // Unless some other external state change happened, the state should now be idle. if (layoutImpl.state.transitionState == transition) { layoutImpl.state.transitionState = TransitionState.Idle(target) } layoutState.finishTransition(transition, target) } } Loading @@ -152,8 +155,16 @@ private class OneOffTransition( override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, override val isUserInputOngoing: Boolean, private val animatable: Animatable<Float, AnimationVector1D>, ) : TransitionState.Transition(fromScene, toScene) { /** * The animatable used to animate this transition. * * Note: This is lateinit because we need to first create this Transition object so that * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to * it, which is need to initialize this Animatable. */ lateinit var animatable: Animatable<Float, AnimationVector1D> override val progress: Float get() = animatable.value } Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +39 −55 Original line number Diff line number Diff line Loading @@ -181,15 +181,11 @@ private data class ElementModifier( } internal class ElementNode( layoutImpl: SceneTransitionLayoutImpl, scene: Scene, element: Element, sceneValues: Element.TargetValues, private var layoutImpl: SceneTransitionLayoutImpl, private var scene: Scene, private var element: Element, private var sceneValues: Element.TargetValues, ) : Modifier.Node(), DrawModifierNode { private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl private var scene: Scene = scene private var element: Element = element private var sceneValues: Element.TargetValues = sceneValues override fun onAttach() { super.onAttach() Loading Loading @@ -283,26 +279,27 @@ private fun shouldDrawElement( scene: Scene, element: Element, ): Boolean { val state = layoutImpl.state.transitionState val transition = layoutImpl.state.currentTransition // Always draw the element if there is no ongoing transition or if the element is not shared. if ( state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state) || state.fromScene !in element.sceneValues || state.toScene !in element.sceneValues transition == null || !layoutImpl.isTransitionReady(transition) || transition.fromScene !in element.sceneValues || transition.toScene !in element.sceneValues ) { return true } val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key) val sharedTransformation = sharedElementTransformation(layoutImpl.state, transition, element.key) if (sharedTransformation?.enabled == false) { return true } return shouldDrawOrComposeSharedElement( layoutImpl, state, transition, scene.key, element.key, sharedTransformation, Loading Loading @@ -331,21 +328,21 @@ internal fun shouldDrawOrComposeSharedElement( } private fun isSharedElementEnabled( layoutImpl: SceneTransitionLayoutImpl, layoutState: SceneTransitionLayoutStateImpl, transition: TransitionState.Transition, element: ElementKey, ): Boolean { return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true return sharedElementTransformation(layoutState, transition, element)?.enabled ?: true } internal fun sharedElementTransformation( layoutImpl: SceneTransitionLayoutImpl, layoutState: SceneTransitionLayoutStateImpl, 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 val transformationSpec = layoutState.transformationSpec val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared // The sharedElement() transformation must either be null or be the same in both scenes. if (sharedInFromScene != sharedInToScene) { Loading @@ -371,13 +368,9 @@ private fun isElementOpaque( scene: Scene, sceneValues: Element.TargetValues, ): Boolean { val state = layoutImpl.state.transitionState if (state !is TransitionState.Transition) { return true } val transition = layoutImpl.state.currentTransition ?: return true if (!layoutImpl.isTransitionReady(state)) { if (!layoutImpl.isTransitionReady(transition)) { val lastValue = sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f Loading @@ -385,8 +378,8 @@ private fun isElementOpaque( return lastValue == 1f } val fromScene = state.fromScene val toScene = state.toScene val fromScene = transition.fromScene val toScene = transition.toScene val fromValues = element.sceneValues[fromScene] val toValues = element.sceneValues[toScene] Loading @@ -395,14 +388,11 @@ private fun isElementOpaque( } val isSharedElement = fromValues != null && toValues != null if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) { return true } return layoutImpl.transitions .transitionSpec(fromScene, toScene) .transformations(element.key, scene.key) .alpha == null return layoutImpl.state.transformationSpec.transformations(element.key, scene.key).alpha == null } /** Loading Loading @@ -607,24 +597,22 @@ private inline fun <T> computeValue( lastValue: () -> T, lerp: (T, T, Float) -> T, ): T { val state = layoutImpl.state.transitionState // There is no ongoing transition. if (state !is TransitionState.Transition) { // Even if this element SceneTransitionLayout is not animated, the layout itself might be // animated (e.g. by another parent SceneTransitionLayout), in which case this element still // need to participate in the layout phase. return currentValue() } val transition = layoutImpl.state.currentTransition // There is no ongoing transition. Even if this element SceneTransitionLayout is not // animated, the layout itself might be animated (e.g. by another parent // SceneTransitionLayout), in which case this element still need to participate in the // layout phase. ?: return currentValue() // A transition was started but it's not ready yet (not all elements have been composed/laid // out yet). Use the last value that was set, to make sure elements don't unexpectedly jump. if (!layoutImpl.isTransitionReady(state)) { if (!layoutImpl.isTransitionReady(transition)) { return lastValue() } val fromScene = state.fromScene val toScene = state.toScene val fromScene = transition.fromScene val toScene = transition.toScene val fromValues = element.sceneValues[fromScene] val toValues = element.sceneValues[toScene] Loading @@ -638,21 +626,17 @@ private inline fun <T> computeValue( // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. val isSharedElement = fromValues != null && toValues != null if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) { val start = sceneValue(fromValues!!) val end = sceneValue(toValues!!) // Make sure we don't read progress if values are the same and we don't need to interpolate, // so we don't invalidate the phase where this is read. return if (start == end) start else lerp(start, end, state.progress) return if (start == end) start else lerp(start, end, transition.progress) } val transformation = transformation( layoutImpl.transitions .transitionSpec(fromScene, toScene) .transformations(element.key, scene.key) ) transformation(layoutImpl.state.transformationSpec.transformations(element.key, scene.key)) // If there is no transformation explicitly associated to this element value, let's use // the value given by the system (like the current position and size given by the layout // pass). Loading @@ -675,7 +659,7 @@ private inline fun <T> computeValue( scene, element, sceneValues, state, transition, idleValue, ) Loading @@ -685,7 +669,7 @@ private inline fun <T> computeValue( return targetValue } val progress = state.progress val progress = transition.progress // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range. val rangeProgress = transformation.range?.progress(progress) ?: progress Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +9 −13 Original line number Diff line number Diff line Loading @@ -120,17 +120,13 @@ private fun shouldComposeMovableElement( scene: SceneKey, element: Element, ): Boolean { val transitionState = layoutImpl.state.transitionState // If we are idle, there is only one [scene] that is composed so we can compose our movable // content here. if (transitionState is TransitionState.Idle) { check(transitionState.currentScene == scene) return true } val fromScene = (transitionState as TransitionState.Transition).fromScene val toScene = transitionState.toScene val transition = layoutImpl.state.currentTransition // If we are idle, there is only one [scene] that is composed so we can compose our // movable content here. ?: return true val fromScene = transition.fromScene val toScene = transition.toScene val fromReady = layoutImpl.isSceneReady(fromScene) val toReady = layoutImpl.isSceneReady(toScene) Loading Loading @@ -181,10 +177,10 @@ private fun shouldComposeMovableElement( return shouldDrawOrComposeSharedElement( layoutImpl, transitionState, transition, scene, element.key, sharedElementTransformation(layoutImpl, transitionState, element.key), sharedElementTransformation(layoutImpl.state, transition, element.key), ) } Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +2 −1 Original line number Diff line number Diff line Loading @@ -179,7 +179,8 @@ private fun scenePriorityNestedScrollConnection( bottomOrRightBehavior: NestedScrollBehavior, ) = SceneNestedScrollHandler( gestureHandler = layoutImpl.gestureHandler(orientation = orientation), layoutImpl = layoutImpl, orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, ) Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +6 −5 Original line number Diff line number Diff line Loading @@ -174,8 +174,8 @@ private fun <T> computeValue( lerp: (T, T, Float) -> T, canOverflow: Boolean, ): T { val state = layoutImpl.state.transitionState if (state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state)) { val transition = layoutImpl.state.currentTransition if (transition == null || !layoutImpl.isTransitionReady(transition)) { return sharedValue.value } Loading @@ -191,10 +191,11 @@ private fun <T> computeValue( return value as Element.SharedValue<T> } val fromValue = sceneValue(state.fromScene) val toValue = sceneValue(state.toScene) val fromValue = sceneValue(transition.fromScene) val toValue = sceneValue(transition.toScene) return if (fromValue != null && toValue != null) { val progress = if (canOverflow) state.progress else state.progress.coerceIn(0f, 1f) val progress = if (canOverflow) transition.progress else transition.progress.coerceIn(0f, 1f) lerp(fromValue.value, toValue.value, progress) } else if (fromValue != null) { fromValue.value Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +47 −36 Original line number Diff line number Diff line Loading @@ -28,11 +28,11 @@ import kotlinx.coroutines.launch * the currently running transition, if there is one. */ internal fun CoroutineScope.animateToScene( layoutImpl: SceneTransitionLayoutImpl, layoutState: SceneTransitionLayoutStateImpl, target: SceneKey, ) { val state = layoutImpl.state.transitionState if (state.currentScene == target) { val transitionState = layoutState.transitionState if (transitionState.currentScene == target) { // This can happen in 3 different situations, for which there isn't anything else to do: // 1. There is no ongoing transition and [target] is already the current scene. // 2. The user is swiping to [target] from another scene and released their pointer such Loading @@ -44,44 +44,47 @@ internal fun CoroutineScope.animateToScene( return } when (state) { is TransitionState.Idle -> animate(layoutImpl, target) when (transitionState) { is TransitionState.Idle -> animate(layoutState, target) is TransitionState.Transition -> { // A transition is currently running: first check whether `transition.toScene` or // `transition.fromScene` is the same as our target scene, in which case the transition // can be accelerated or reversed to end up in the target state. if (state.toScene == target) { if (transitionState.toScene == target) { // The user is currently swiping to [target] but didn't release their pointer yet: // animate the progress to `1`. check(state.fromScene == state.currentScene) val progress = state.progress check(transitionState.fromScene == transitionState.currentScene) val progress = transitionState.progress if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) { // The transition is already finished (progress ~= 1): no need to animate. layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene) // The transition is already finished (progress ~= 1): no need to animate. We // finish the current transition early to make sure that the current state // change is committed. layoutState.finishTransition(transitionState, transitionState.currentScene) } else { // The transition is in progress: start the canned animation at the same // progress as it was in. // TODO(b/290184746): Also take the current velocity into account. animate(layoutImpl, target, startProgress = progress) animate(layoutState, target, startProgress = progress) } return } if (state.fromScene == target) { if (transitionState.fromScene == target) { // There is a transition from [target] to another scene: simply animate the same // transition progress to `0`. check(state.toScene == state.currentScene) val progress = state.progress check(transitionState.toScene == transitionState.currentScene) val progress = transitionState.progress if (progress.absoluteValue < ProgressVisibilityThreshold) { // The transition is at progress ~= 0: no need to animate. layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene) // The transition is at progress ~= 0: no need to animate.We finish the current // transition early to make sure that the current state change is committed. layoutState.finishTransition(transitionState, transitionState.currentScene) } else { // TODO(b/290184746): Also take the current velocity into account. animate(layoutImpl, target, startProgress = progress, reversed = true) animate(layoutState, target, startProgress = progress, reversed = true) } return Loading @@ -89,27 +92,22 @@ internal fun CoroutineScope.animateToScene( // Generic interruption; the current transition is neither from or to [target]. // TODO(b/290930950): Better handle interruptions here. animate(layoutImpl, target) animate(layoutState, target) } } } private fun CoroutineScope.animate( layoutImpl: SceneTransitionLayoutImpl, layoutState: SceneTransitionLayoutStateImpl, target: SceneKey, startProgress: Float = 0f, reversed: Boolean = false, ) { val fromScene = layoutImpl.state.transitionState.currentScene val fromScene = layoutState.transitionState.currentScene val isUserInput = (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput (layoutState.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput ?: false val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec val visibilityThreshold = (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold) val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { Loading @@ -119,7 +117,6 @@ private fun CoroutineScope.animate( currentScene = target, isInitiatedByUserInput = isUserInput, isUserInputOngoing = false, animatable = animatable, ) } else { OneOffTransition( Loading @@ -128,21 +125,27 @@ private fun CoroutineScope.animate( currentScene = target, isInitiatedByUserInput = isUserInput, isUserInputOngoing = false, animatable = animatable, ) } // Change the current layout state to use this new transition. layoutImpl.state.transitionState = transition // Change the current layout state to start this new transition. This will compute the // TransformationSpec associated to this transition, which we need to initialize the Animatable // that will actually animate it. layoutState.startTransition(transition) // The transformation now contains the spec that we should use to instantiate the Animatable. val animationSpec = layoutState.transformationSpec.progressSpec val visibilityThreshold = (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold).also { transition.animatable = it } // Animate the progress to its target value. launch { animatable.animateTo(targetProgress, animationSpec) // Unless some other external state change happened, the state should now be idle. if (layoutImpl.state.transitionState == transition) { layoutImpl.state.transitionState = TransitionState.Idle(target) } layoutState.finishTransition(transition, target) } } Loading @@ -152,8 +155,16 @@ private class OneOffTransition( override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, override val isUserInputOngoing: Boolean, private val animatable: Animatable<Float, AnimationVector1D>, ) : TransitionState.Transition(fromScene, toScene) { /** * The animatable used to animate this transition. * * Note: This is lateinit because we need to first create this Transition object so that * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to * it, which is need to initialize this Animatable. */ lateinit var animatable: Animatable<Float, AnimationVector1D> override val progress: Float get() = animatable.value } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +39 −55 Original line number Diff line number Diff line Loading @@ -181,15 +181,11 @@ private data class ElementModifier( } internal class ElementNode( layoutImpl: SceneTransitionLayoutImpl, scene: Scene, element: Element, sceneValues: Element.TargetValues, private var layoutImpl: SceneTransitionLayoutImpl, private var scene: Scene, private var element: Element, private var sceneValues: Element.TargetValues, ) : Modifier.Node(), DrawModifierNode { private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl private var scene: Scene = scene private var element: Element = element private var sceneValues: Element.TargetValues = sceneValues override fun onAttach() { super.onAttach() Loading Loading @@ -283,26 +279,27 @@ private fun shouldDrawElement( scene: Scene, element: Element, ): Boolean { val state = layoutImpl.state.transitionState val transition = layoutImpl.state.currentTransition // Always draw the element if there is no ongoing transition or if the element is not shared. if ( state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state) || state.fromScene !in element.sceneValues || state.toScene !in element.sceneValues transition == null || !layoutImpl.isTransitionReady(transition) || transition.fromScene !in element.sceneValues || transition.toScene !in element.sceneValues ) { return true } val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key) val sharedTransformation = sharedElementTransformation(layoutImpl.state, transition, element.key) if (sharedTransformation?.enabled == false) { return true } return shouldDrawOrComposeSharedElement( layoutImpl, state, transition, scene.key, element.key, sharedTransformation, Loading Loading @@ -331,21 +328,21 @@ internal fun shouldDrawOrComposeSharedElement( } private fun isSharedElementEnabled( layoutImpl: SceneTransitionLayoutImpl, layoutState: SceneTransitionLayoutStateImpl, transition: TransitionState.Transition, element: ElementKey, ): Boolean { return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true return sharedElementTransformation(layoutState, transition, element)?.enabled ?: true } internal fun sharedElementTransformation( layoutImpl: SceneTransitionLayoutImpl, layoutState: SceneTransitionLayoutStateImpl, 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 val transformationSpec = layoutState.transformationSpec val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared // The sharedElement() transformation must either be null or be the same in both scenes. if (sharedInFromScene != sharedInToScene) { Loading @@ -371,13 +368,9 @@ private fun isElementOpaque( scene: Scene, sceneValues: Element.TargetValues, ): Boolean { val state = layoutImpl.state.transitionState if (state !is TransitionState.Transition) { return true } val transition = layoutImpl.state.currentTransition ?: return true if (!layoutImpl.isTransitionReady(state)) { if (!layoutImpl.isTransitionReady(transition)) { val lastValue = sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f Loading @@ -385,8 +378,8 @@ private fun isElementOpaque( return lastValue == 1f } val fromScene = state.fromScene val toScene = state.toScene val fromScene = transition.fromScene val toScene = transition.toScene val fromValues = element.sceneValues[fromScene] val toValues = element.sceneValues[toScene] Loading @@ -395,14 +388,11 @@ private fun isElementOpaque( } val isSharedElement = fromValues != null && toValues != null if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) { return true } return layoutImpl.transitions .transitionSpec(fromScene, toScene) .transformations(element.key, scene.key) .alpha == null return layoutImpl.state.transformationSpec.transformations(element.key, scene.key).alpha == null } /** Loading Loading @@ -607,24 +597,22 @@ private inline fun <T> computeValue( lastValue: () -> T, lerp: (T, T, Float) -> T, ): T { val state = layoutImpl.state.transitionState // There is no ongoing transition. if (state !is TransitionState.Transition) { // Even if this element SceneTransitionLayout is not animated, the layout itself might be // animated (e.g. by another parent SceneTransitionLayout), in which case this element still // need to participate in the layout phase. return currentValue() } val transition = layoutImpl.state.currentTransition // There is no ongoing transition. Even if this element SceneTransitionLayout is not // animated, the layout itself might be animated (e.g. by another parent // SceneTransitionLayout), in which case this element still need to participate in the // layout phase. ?: return currentValue() // A transition was started but it's not ready yet (not all elements have been composed/laid // out yet). Use the last value that was set, to make sure elements don't unexpectedly jump. if (!layoutImpl.isTransitionReady(state)) { if (!layoutImpl.isTransitionReady(transition)) { return lastValue() } val fromScene = state.fromScene val toScene = state.toScene val fromScene = transition.fromScene val toScene = transition.toScene val fromValues = element.sceneValues[fromScene] val toValues = element.sceneValues[toScene] Loading @@ -638,21 +626,17 @@ private inline fun <T> computeValue( // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. val isSharedElement = fromValues != null && toValues != null if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) { val start = sceneValue(fromValues!!) val end = sceneValue(toValues!!) // Make sure we don't read progress if values are the same and we don't need to interpolate, // so we don't invalidate the phase where this is read. return if (start == end) start else lerp(start, end, state.progress) return if (start == end) start else lerp(start, end, transition.progress) } val transformation = transformation( layoutImpl.transitions .transitionSpec(fromScene, toScene) .transformations(element.key, scene.key) ) transformation(layoutImpl.state.transformationSpec.transformations(element.key, scene.key)) // If there is no transformation explicitly associated to this element value, let's use // the value given by the system (like the current position and size given by the layout // pass). Loading @@ -675,7 +659,7 @@ private inline fun <T> computeValue( scene, element, sceneValues, state, transition, idleValue, ) Loading @@ -685,7 +669,7 @@ private inline fun <T> computeValue( return targetValue } val progress = state.progress val progress = transition.progress // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range. val rangeProgress = transformation.range?.progress(progress) ?: progress Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +9 −13 Original line number Diff line number Diff line Loading @@ -120,17 +120,13 @@ private fun shouldComposeMovableElement( scene: SceneKey, element: Element, ): Boolean { val transitionState = layoutImpl.state.transitionState // If we are idle, there is only one [scene] that is composed so we can compose our movable // content here. if (transitionState is TransitionState.Idle) { check(transitionState.currentScene == scene) return true } val fromScene = (transitionState as TransitionState.Transition).fromScene val toScene = transitionState.toScene val transition = layoutImpl.state.currentTransition // If we are idle, there is only one [scene] that is composed so we can compose our // movable content here. ?: return true val fromScene = transition.fromScene val toScene = transition.toScene val fromReady = layoutImpl.isSceneReady(fromScene) val toReady = layoutImpl.isSceneReady(toScene) Loading Loading @@ -181,10 +177,10 @@ private fun shouldComposeMovableElement( return shouldDrawOrComposeSharedElement( layoutImpl, transitionState, transition, scene, element.key, sharedElementTransformation(layoutImpl, transitionState, element.key), sharedElementTransformation(layoutImpl.state, transition, element.key), ) } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +2 −1 Original line number Diff line number Diff line Loading @@ -179,7 +179,8 @@ private fun scenePriorityNestedScrollConnection( bottomOrRightBehavior: NestedScrollBehavior, ) = SceneNestedScrollHandler( gestureHandler = layoutImpl.gestureHandler(orientation = orientation), layoutImpl = layoutImpl, orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, ) Loading