Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +92 −4 Original line number Diff line number Diff line Loading @@ -1133,10 +1133,98 @@ private inline fun <T> computeValue( val transformation = transformation(transition.transformationSpec.transformations(element.key, scene)) val previewTransformation = transition.previewTransformationSpec?.let { transformation(it.transformations(element.key, scene)) } if (previewTransformation != null) { val isInPreviewStage = transition.isInPreviewStage val idleValue = sceneValue(sceneState) val isEntering = scene == toScene val previewTargetValue = previewTransformation.transform( layoutImpl, scene, element, sceneState, transition, idleValue, ) val targetValueOrNull = transformation?.transform( layoutImpl, scene, element, sceneState, transition, idleValue, ) // 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. when { isInPreviewStage && isEntering && previewTargetValue == targetValueOrNull -> return previewTargetValue isInPreviewStage && !isEntering && idleValue == previewTargetValue -> return idleValue previewTargetValue == targetValueOrNull && idleValue == previewTargetValue -> return idleValue else -> {} } val previewProgress = transition.previewProgress // progress is not needed for all cases of the below when block, therefore read it lazily // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range val previewRangeProgress = previewTransformation.range?.progress(previewProgress) ?: previewProgress if (isInPreviewStage) { // if we're in the preview stage of the transition, interpolate between start state and // preview target state: return if (isEntering) { // i.e. in the entering case between previewTargetValue and targetValue (or // idleValue if no transformation is defined in the second stage transition)... lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress) } else { // ...and in the exiting case between the idleValue and the previewTargetValue. lerp(idleValue, previewTargetValue, previewRangeProgress) } } // if we're in the second stage of the transition, interpolate between the state the // element was left at the end of the preview-phase and the target state: return if (isEntering) { // i.e. in the entering case between preview-end-state and the idleValue... lerp( lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress), idleValue, transformation?.range?.progress(transition.progress) ?: transition.progress ) } else { if (targetValueOrNull == null) { // ... and in the exiting case, the element should remain in the preview-end-state // if no further transformation is defined in the second-stage transition... lerp(idleValue, previewTargetValue, previewRangeProgress) } else { // ...and otherwise it should be interpolated between preview-end-state and // targetValue lerp( lerp(idleValue, previewTargetValue, previewRangeProgress), targetValueOrNull, transformation.range?.progress(transition.progress) ?: transition.progress ) } } } if (transformation == null) { // 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). ?: return currentValue() return currentValue() } val idleValue = sceneValue(sceneState) val targetValue = Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +12 −3 Original line number Diff line number Diff line Loading @@ -77,8 +77,17 @@ private class PredictiveBackTransition( private var progressAnimatable by mutableStateOf<Animatable<Float, AnimationVector1D>?>(null) var dragProgress: Float by mutableFloatStateOf(0f) override val previewProgress: Float get() = dragProgress override val previewProgressVelocity: Float get() = 0f // Currently, velocity is not exposed by predictive back API override val isInPreviewStage: Boolean get() = progressAnimatable == null && previewTransformationSpec != null override val progress: Float get() = progressAnimatable?.value ?: dragProgress get() = progressAnimatable?.value ?: previewTransformationSpec?.let { 0f } ?: dragProgress override val progressVelocity: Float get() = progressAnimatable?.velocity ?: 0f Loading Loading @@ -109,8 +118,8 @@ private class PredictiveBackTransition( toScene -> 1f else -> error("scene $currentScene should be either $fromScene or $toScene") } val animatable = Animatable(dragProgress).also { progressAnimatable = it } val startProgress = if (previewTransformationSpec != null) 0f else dragProgress val animatable = Animatable(startProgress).also { progressAnimatable = it } // Important: We start atomically to make sure that we start the coroutine even if it is // cancelled right after it is launched, so that finishTransition() is correctly called. Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +18 −0 Original line number Diff line number Diff line Loading @@ -189,6 +189,19 @@ sealed interface TransitionState { /** The current velocity of [progress], in progress units. */ abstract val progressVelocity: Float /** * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can * also be less than `0` or greater than `1` when using transitions with a spring * AnimationSpec or when flinging quickly during a swipe gesture. */ open val previewProgress: Float = 0f /** The current velocity of [previewProgress], in progress units. */ open val previewProgressVelocity: Float = 0f /** Whether the transition is currently in the preview stage */ open val isInPreviewStage: Boolean = false /** Whether the transition was triggered by user input rather than being programmatic. */ abstract val isInitiatedByUserInput: Boolean Loading @@ -203,6 +216,7 @@ sealed interface TransitionState { * [started][MutableSceneTransitionLayoutStateImpl.startTransition]. */ internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty internal var previewTransformationSpec: TransformationSpecImpl? = null private var fromOverscrollSpec: OverscrollSpecImpl? = null private var toOverscrollSpec: OverscrollSpecImpl? = null Loading Loading @@ -437,6 +451,10 @@ internal class MutableSceneTransitionLayoutStateImpl( transitions .transitionSpec(fromScene, toScene, key = transition.key) .transformationSpec() transition.previewTransformationSpec = transitions .transitionSpec(fromScene, toScene, key = transition.key) .previewTransformationSpec() if (orientation != null) { transition.updateOverscrollSpecs( fromSpec = transitions.overscrollSpec(fromScene, orientation), Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +18 −3 Original line number Diff line number Diff line Loading @@ -110,7 +110,7 @@ internal constructor( } private fun defaultTransition(from: SceneKey, to: SceneKey) = TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider) TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider) internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? = overscrollCache Loading Loading @@ -177,10 +177,18 @@ interface TransitionSpec { /** * The [TransformationSpec] associated to this [TransitionSpec]. * * Note that this is called once every a transition associated to this [TransitionSpec] is * Note that this is called once whenever a transition associated to this [TransitionSpec] is * started. */ fun transformationSpec(): TransformationSpec /** * The preview [TransformationSpec] associated to this [TransitionSpec]. * * Note that this is called once whenever a transition associated to this [TransitionSpec] is * started. */ fun previewTransformationSpec(): TransformationSpec? } interface TransformationSpec { Loading Loading @@ -225,13 +233,17 @@ internal class TransitionSpecImpl( override val key: TransitionKey?, override val from: SceneKey?, override val to: SceneKey?, private val transformationSpec: () -> TransformationSpecImpl, private val previewTransformationSpec: (() -> TransformationSpecImpl)? = null, private val reversePreviewTransformationSpec: (() -> TransformationSpecImpl)? = null, private val transformationSpec: () -> TransformationSpecImpl ) : TransitionSpec { override fun reversed(): TransitionSpecImpl { return TransitionSpecImpl( key = key, from = to, to = from, previewTransformationSpec = reversePreviewTransformationSpec, reversePreviewTransformationSpec = previewTransformationSpec, transformationSpec = { val reverse = transformationSpec.invoke() TransformationSpecImpl( Loading @@ -245,6 +257,9 @@ internal class TransitionSpecImpl( } override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke() override fun previewTransformationSpec(): TransformationSpecImpl? = previewTransformationSpec?.invoke() } /** The definition of the overscroll behavior of the [scene]. */ Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +12 −0 Original line number Diff line number Diff line Loading @@ -54,11 +54,17 @@ interface SceneTransitionsBuilder { * If [key] is not `null`, then this transition will only be used if the same key is specified * when triggering the transition. * * Optionally, define a [preview] animation which will be played during the first stage of the * transition, e.g. during the predictive back gesture. In case your transition should be * reversible with the reverse animation having a preview as well, define a [reversePreview]. * * @see from */ fun to( to: SceneKey, key: TransitionKey? = null, preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, builder: TransitionBuilder.() -> Unit = {}, ): TransitionSpec Loading @@ -74,11 +80,17 @@ interface SceneTransitionsBuilder { * 2. to == A && from == B, which is then treated in reverse. * 3. (from == A && to == null) || (from == null && to == B) * 4. (from == B && to == null) || (from == null && to == A), which is then treated in reverse. * * Optionally, define a [preview] animation which will be played during the first stage of the * transition, e.g. during the predictive back gesture. In case your transition should be * reversible with the reverse animation having a preview as well, define a [reversePreview]. */ fun from( from: SceneKey, to: SceneKey? = null, key: TransitionKey? = null, preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, builder: TransitionBuilder.() -> Unit = {}, ): TransitionSpec Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +92 −4 Original line number Diff line number Diff line Loading @@ -1133,10 +1133,98 @@ private inline fun <T> computeValue( val transformation = transformation(transition.transformationSpec.transformations(element.key, scene)) val previewTransformation = transition.previewTransformationSpec?.let { transformation(it.transformations(element.key, scene)) } if (previewTransformation != null) { val isInPreviewStage = transition.isInPreviewStage val idleValue = sceneValue(sceneState) val isEntering = scene == toScene val previewTargetValue = previewTransformation.transform( layoutImpl, scene, element, sceneState, transition, idleValue, ) val targetValueOrNull = transformation?.transform( layoutImpl, scene, element, sceneState, transition, idleValue, ) // 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. when { isInPreviewStage && isEntering && previewTargetValue == targetValueOrNull -> return previewTargetValue isInPreviewStage && !isEntering && idleValue == previewTargetValue -> return idleValue previewTargetValue == targetValueOrNull && idleValue == previewTargetValue -> return idleValue else -> {} } val previewProgress = transition.previewProgress // progress is not needed for all cases of the below when block, therefore read it lazily // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range val previewRangeProgress = previewTransformation.range?.progress(previewProgress) ?: previewProgress if (isInPreviewStage) { // if we're in the preview stage of the transition, interpolate between start state and // preview target state: return if (isEntering) { // i.e. in the entering case between previewTargetValue and targetValue (or // idleValue if no transformation is defined in the second stage transition)... lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress) } else { // ...and in the exiting case between the idleValue and the previewTargetValue. lerp(idleValue, previewTargetValue, previewRangeProgress) } } // if we're in the second stage of the transition, interpolate between the state the // element was left at the end of the preview-phase and the target state: return if (isEntering) { // i.e. in the entering case between preview-end-state and the idleValue... lerp( lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress), idleValue, transformation?.range?.progress(transition.progress) ?: transition.progress ) } else { if (targetValueOrNull == null) { // ... and in the exiting case, the element should remain in the preview-end-state // if no further transformation is defined in the second-stage transition... lerp(idleValue, previewTargetValue, previewRangeProgress) } else { // ...and otherwise it should be interpolated between preview-end-state and // targetValue lerp( lerp(idleValue, previewTargetValue, previewRangeProgress), targetValueOrNull, transformation.range?.progress(transition.progress) ?: transition.progress ) } } } if (transformation == null) { // 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). ?: return currentValue() return currentValue() } val idleValue = sceneValue(sceneState) val targetValue = Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +12 −3 Original line number Diff line number Diff line Loading @@ -77,8 +77,17 @@ private class PredictiveBackTransition( private var progressAnimatable by mutableStateOf<Animatable<Float, AnimationVector1D>?>(null) var dragProgress: Float by mutableFloatStateOf(0f) override val previewProgress: Float get() = dragProgress override val previewProgressVelocity: Float get() = 0f // Currently, velocity is not exposed by predictive back API override val isInPreviewStage: Boolean get() = progressAnimatable == null && previewTransformationSpec != null override val progress: Float get() = progressAnimatable?.value ?: dragProgress get() = progressAnimatable?.value ?: previewTransformationSpec?.let { 0f } ?: dragProgress override val progressVelocity: Float get() = progressAnimatable?.velocity ?: 0f Loading Loading @@ -109,8 +118,8 @@ private class PredictiveBackTransition( toScene -> 1f else -> error("scene $currentScene should be either $fromScene or $toScene") } val animatable = Animatable(dragProgress).also { progressAnimatable = it } val startProgress = if (previewTransformationSpec != null) 0f else dragProgress val animatable = Animatable(startProgress).also { progressAnimatable = it } // Important: We start atomically to make sure that we start the coroutine even if it is // cancelled right after it is launched, so that finishTransition() is correctly called. Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +18 −0 Original line number Diff line number Diff line Loading @@ -189,6 +189,19 @@ sealed interface TransitionState { /** The current velocity of [progress], in progress units. */ abstract val progressVelocity: Float /** * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can * also be less than `0` or greater than `1` when using transitions with a spring * AnimationSpec or when flinging quickly during a swipe gesture. */ open val previewProgress: Float = 0f /** The current velocity of [previewProgress], in progress units. */ open val previewProgressVelocity: Float = 0f /** Whether the transition is currently in the preview stage */ open val isInPreviewStage: Boolean = false /** Whether the transition was triggered by user input rather than being programmatic. */ abstract val isInitiatedByUserInput: Boolean Loading @@ -203,6 +216,7 @@ sealed interface TransitionState { * [started][MutableSceneTransitionLayoutStateImpl.startTransition]. */ internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty internal var previewTransformationSpec: TransformationSpecImpl? = null private var fromOverscrollSpec: OverscrollSpecImpl? = null private var toOverscrollSpec: OverscrollSpecImpl? = null Loading Loading @@ -437,6 +451,10 @@ internal class MutableSceneTransitionLayoutStateImpl( transitions .transitionSpec(fromScene, toScene, key = transition.key) .transformationSpec() transition.previewTransformationSpec = transitions .transitionSpec(fromScene, toScene, key = transition.key) .previewTransformationSpec() if (orientation != null) { transition.updateOverscrollSpecs( fromSpec = transitions.overscrollSpec(fromScene, orientation), Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +18 −3 Original line number Diff line number Diff line Loading @@ -110,7 +110,7 @@ internal constructor( } private fun defaultTransition(from: SceneKey, to: SceneKey) = TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider) TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider) internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? = overscrollCache Loading Loading @@ -177,10 +177,18 @@ interface TransitionSpec { /** * The [TransformationSpec] associated to this [TransitionSpec]. * * Note that this is called once every a transition associated to this [TransitionSpec] is * Note that this is called once whenever a transition associated to this [TransitionSpec] is * started. */ fun transformationSpec(): TransformationSpec /** * The preview [TransformationSpec] associated to this [TransitionSpec]. * * Note that this is called once whenever a transition associated to this [TransitionSpec] is * started. */ fun previewTransformationSpec(): TransformationSpec? } interface TransformationSpec { Loading Loading @@ -225,13 +233,17 @@ internal class TransitionSpecImpl( override val key: TransitionKey?, override val from: SceneKey?, override val to: SceneKey?, private val transformationSpec: () -> TransformationSpecImpl, private val previewTransformationSpec: (() -> TransformationSpecImpl)? = null, private val reversePreviewTransformationSpec: (() -> TransformationSpecImpl)? = null, private val transformationSpec: () -> TransformationSpecImpl ) : TransitionSpec { override fun reversed(): TransitionSpecImpl { return TransitionSpecImpl( key = key, from = to, to = from, previewTransformationSpec = reversePreviewTransformationSpec, reversePreviewTransformationSpec = previewTransformationSpec, transformationSpec = { val reverse = transformationSpec.invoke() TransformationSpecImpl( Loading @@ -245,6 +257,9 @@ internal class TransitionSpecImpl( } override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke() override fun previewTransformationSpec(): TransformationSpecImpl? = previewTransformationSpec?.invoke() } /** The definition of the overscroll behavior of the [scene]. */ Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +12 −0 Original line number Diff line number Diff line Loading @@ -54,11 +54,17 @@ interface SceneTransitionsBuilder { * If [key] is not `null`, then this transition will only be used if the same key is specified * when triggering the transition. * * Optionally, define a [preview] animation which will be played during the first stage of the * transition, e.g. during the predictive back gesture. In case your transition should be * reversible with the reverse animation having a preview as well, define a [reversePreview]. * * @see from */ fun to( to: SceneKey, key: TransitionKey? = null, preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, builder: TransitionBuilder.() -> Unit = {}, ): TransitionSpec Loading @@ -74,11 +80,17 @@ interface SceneTransitionsBuilder { * 2. to == A && from == B, which is then treated in reverse. * 3. (from == A && to == null) || (from == null && to == B) * 4. (from == B && to == null) || (from == null && to == A), which is then treated in reverse. * * Optionally, define a [preview] animation which will be played during the first stage of the * transition, e.g. during the predictive back gesture. In case your transition should be * reversible with the reverse animation having a preview as well, define a [reversePreview]. */ fun from( from: SceneKey, to: SceneKey? = null, key: TransitionKey? = null, preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, builder: TransitionBuilder.() -> Unit = {}, ): TransitionSpec Loading