Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +57 −9 Original line number Diff line number Diff line Loading @@ -39,7 +39,6 @@ import androidx.compose.ui.layout.Placeable import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.testTag import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.round Loading Loading @@ -204,6 +203,17 @@ internal class ElementNode( measurable: Measurable, constraints: Constraints, ): MeasureResult { val overscrollScene = layoutImpl.state.currentOverscrollSpec?.scene if (overscrollScene != null && overscrollScene != scene.key) { // There is an overscroll in progress on another scene // By measuring composable elements, Compose can cache relevant information. // This reduces the need for re-measure when users return from an overscroll animation. val placeable = measurable.measure(constraints) return layout(placeable.width, placeable.height) { // We don't want to draw it, no need to place the element. } } val placeable = measure(layoutImpl, scene, element, sceneState, measurable, constraints) return layout(placeable.width, placeable.height) { place(layoutImpl, scene, element, sceneState, placeable, placementScope = this) Loading Loading @@ -253,11 +263,13 @@ private fun shouldDrawElement( ): Boolean { val transition = layoutImpl.state.currentTransition // Always draw the element if there is no ongoing transition or if the element is not shared. // Always draw the element if there is no ongoing transition or if the element is not shared or // if the current scene is the one that is currently over scrolling with [OverscrollSpec]. if ( transition == null || transition.fromScene !in element.sceneStates || transition.toScene !in element.sceneStates transition.toScene !in element.sceneStates || layoutImpl.state.currentOverscrollSpec?.scene == scene.key ) { return true } Loading Loading @@ -286,12 +298,14 @@ internal fun shouldDrawOrComposeSharedElement( val fromScene = transition.fromScene val toScene = transition.toScene return scenePicker.sceneDuringTransition( val chosenByPicker = scenePicker.sceneDuringTransition( element = element, transition = transition, fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex, toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex, ) == scene return chosenByPicker || layoutImpl.state.currentOverscrollSpec?.scene == scene } private fun isSharedElementEnabled( Loading Loading @@ -549,6 +563,40 @@ private inline fun <T> computeValue( return idleValue } if (transition is TransitionState.HasOverscrollProperties) { val overscroll = layoutImpl.state.currentOverscrollSpec if (overscroll?.scene == scene.key) { val elementSpec = overscroll.transformationSpec.transformations(element.key, scene.key) val propertySpec = transformation(elementSpec) ?: return currentValue() val overscrollState = checkNotNull(if (scene.key == toScene) toState else fromState) val targetValue = propertySpec.transform( layoutImpl, scene, element, overscrollState, 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. if (targetValue == idleValue) { return targetValue } // TODO(b/290184746): Make sure that we don't overflow transformations associated to a // range. val directionSign = if (transition.isUpOrLeft) -1 else 1 val overscrollProgress = transition.progress.let { if (it > 1f) it - 1f else it } val progress = directionSign * overscrollProgress val rangeProgress = propertySpec.range?.progress(progress) ?: progress // Interpolate between the value at rest and the over scrolled value. return lerp(idleValue, targetValue, rangeProgress) } } // The element is shared: interpolate between the value in fromScene and the value in toScene. // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +14 −5 Original line number Diff line number Diff line Loading @@ -497,9 +497,11 @@ private class SwipeTransition( val _fromScene: Scene, val _toScene: Scene, private val userActionDistanceScope: UserActionDistanceScope, private val orientation: Orientation, private val isUpOrLeft: Boolean, ) : TransitionState.Transition(_fromScene.key, _toScene.key) { override val orientation: Orientation, override val isUpOrLeft: Boolean, ) : TransitionState.Transition(_fromScene.key, _toScene.key), TransitionState.HasOverscrollProperties { var _currentScene by mutableStateOf(_fromScene) override val currentScene: SceneKey get() = _currentScene.key Loading Loading @@ -789,14 +791,21 @@ internal class SceneNestedScrollHandler( ) fun hasNextScene(amount: Float): Boolean { val fromScene = layoutImpl.scene(layoutState.transitionState.currentScene) val transitionState = layoutState.transitionState val scene = transitionState.currentScene val fromScene = layoutImpl.scene(scene) val nextScene = when { amount < 0f -> fromScene.userActions[actionUpOrLeft] amount > 0f -> fromScene.userActions[actionDownOrRight] else -> null } return nextScene != null if (nextScene != null) return true if (transitionState !is TransitionState.Idle) return false val overscrollSpec = layoutImpl.state.transitions.overscrollSpec(scene, orientation) return overscrollSpec != null } val source = this Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +43 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect Loading Loading @@ -221,6 +222,23 @@ sealed interface TransitionState { isTransitioning(from = other, to = scene) } } interface HasOverscrollProperties { /** * The position of the [TransitionState.Transition.toScene]. * * Used to understand the direction of the overscroll. */ val isUpOrLeft: Boolean /** * The relative orientation between [TransitionState.Transition.fromScene] and * [TransitionState.Transition.toScene]. * * Used to understand the orientation of the overscroll. */ val orientation: Orientation } } internal abstract class BaseSceneTransitionLayoutState( Loading @@ -237,6 +255,25 @@ internal abstract class BaseSceneTransitionLayoutState( */ internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty private var fromOverscrollSpec: OverscrollSpecImpl? = null private var toOverscrollSpec: OverscrollSpecImpl? = null /** * @return the overscroll [OverscrollSpecImpl] if it is defined for the current * [transitionState] and we are currently over scrolling. */ internal val currentOverscrollSpec: OverscrollSpecImpl? get() { val transition = currentTransition ?: return null if (transition !is TransitionState.HasOverscrollProperties) return null val progress = transition.progress return when { progress < 0f -> fromOverscrollSpec progress > 1f -> toOverscrollSpec else -> null } } private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() /** Whether we can transition to the given [scene]. */ Loading Loading @@ -266,10 +303,13 @@ internal abstract class BaseSceneTransitionLayoutState( transitionKey: TransitionKey?, ) { // Compute the [TransformationSpec] when the transition starts. val fromScene = transition.fromScene val toScene = transition.toScene val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation transformationSpec = transitions .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey) .transformationSpec() transitions.transitionSpec(fromScene, toScene, key = transitionKey).transformationSpec() fromOverscrollSpec = orientation?.let { transitions.overscrollSpec(fromScene, it) } toOverscrollSpec = orientation?.let { transitions.overscrollSpec(toScene, it) } cancelActiveTransitionLinks() setupTransitionLinks(transition) transitionState = transition Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +54 −4 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import androidx.compose.animation.core.Spring import androidx.compose.animation.core.SpringSpec import androidx.compose.animation.core.snap import androidx.compose.animation.core.spring import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastForEach Loading @@ -41,18 +42,22 @@ class SceneTransitions internal constructor( internal val defaultSwipeSpec: SpringSpec<Float>, internal val transitionSpecs: List<TransitionSpecImpl>, internal val overscrollSpecs: List<OverscrollSpecImpl>, ) { private val cache = private val transitionCache = mutableMapOf< SceneKey, MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>> >() private val overscrollCache = mutableMapOf<SceneKey, MutableMap<Orientation, OverscrollSpecImpl?>>() internal fun transitionSpec( from: SceneKey, to: SceneKey, key: TransitionKey?, ): TransitionSpecImpl { return cache return transitionCache .getOrPut(from) { mutableMapOf() } .getOrPut(to) { mutableMapOf() } .getOrPut(key) { findSpec(from, to, key) } Loading Loading @@ -105,6 +110,28 @@ internal constructor( private fun defaultTransition(from: SceneKey, to: SceneKey) = TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider) internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? = overscrollCache .getOrPut(scene) { mutableMapOf() } .getOrPut(orientation) { overscroll(scene, orientation) { it.scene == scene } } private fun overscroll( scene: SceneKey, orientation: Orientation, filter: (OverscrollSpecImpl) -> Boolean, ): OverscrollSpecImpl? { var match: OverscrollSpecImpl? = null overscrollSpecs.fastForEach { spec -> if (spec.orientation == orientation && filter(spec)) { if (match != null) { error("Found multiple transition specs for transition $scene") } match = spec } } return match } companion object { internal val DefaultSwipeSpec = spring( Loading @@ -112,7 +139,12 @@ internal constructor( visibilityThreshold = OffsetVisibilityThreshold, ) val Empty = SceneTransitions(DefaultSwipeSpec, transitionSpecs = emptyList()) val Empty = SceneTransitions( defaultSwipeSpec = DefaultSwipeSpec, transitionSpecs = emptyList(), overscrollSpecs = emptyList(), ) } } Loading @@ -139,7 +171,7 @@ interface TransitionSpec { */ fun reversed(): TransitionSpec /* /** * The [TransformationSpec] associated to this [TransitionSpec]. * * Note that this is called once every a transition associated to this [TransitionSpec] is Loading Loading @@ -212,6 +244,24 @@ internal class TransitionSpecImpl( override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke() } /** The definition of the overscroll behavior of the [scene]. */ interface OverscrollSpec { /** The scene we are over scrolling. */ val scene: SceneKey /** The orientation of this [OverscrollSpec]. */ val orientation: Orientation /** The [TransformationSpec] associated to this [OverscrollSpec]. */ val transformationSpec: TransformationSpec } internal class OverscrollSpecImpl( override val scene: SceneKey, override val orientation: Orientation, override val transformationSpec: TransformationSpecImpl, ) : OverscrollSpec /** * An implementation of [TransformationSpec] that allows the quick retrieval of an element * [ElementTransformations]. Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +30 −13 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp Loading Loading @@ -72,24 +73,23 @@ interface SceneTransitionsBuilder { key: TransitionKey? = null, builder: TransitionBuilder.() -> Unit = {}, ): TransitionSpec } @TransitionDsl interface TransitionBuilder : PropertyTransformationBuilder { /** * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when * the transition is triggered (i.e. it is not gesture-based). */ var spec: AnimationSpec<Float> /** * The [SpringSpec] used to animate the associated transition progress when the transition was * started by a swipe and is now animating back to a scene because the user lifted their finger. * Define the animation to be played when the [scene] is overscrolled in the given * [orientation]. * * If `null`, then the [SceneTransitionsBuilder.defaultSwipeSpec] will be used. * The overscroll animation always starts from a progress of 0f, and reaches 1f when moving the * [distance] down/right, -1f when moving in the opposite direction. */ var swipeSpec: SpringSpec<Float>? fun overscroll( scene: SceneKey, orientation: Orientation, builder: OverscrollBuilder.() -> Unit = {}, ): OverscrollSpec } @TransitionDsl interface OverscrollBuilder : PropertyTransformationBuilder { /** * The distance it takes for this transition to animate from 0% to 100% when it is driven by a * [UserAction]. Loading Loading @@ -117,6 +117,23 @@ interface TransitionBuilder : PropertyTransformationBuilder { end: Float? = null, builder: PropertyTransformationBuilder.() -> Unit, ) } @TransitionDsl interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder { /** * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when * the transition is triggered (i.e. it is not gesture-based). */ var spec: AnimationSpec<Float> /** * The [SpringSpec] used to animate the associated transition progress when the transition was * started by a swipe and is now animating back to a scene because the user lifted their finger. * * If `null`, then the [SceneTransitionsBuilder.defaultSwipeSpec] will be used. */ var swipeSpec: SpringSpec<Float>? /** * Define a timestamp-based range for the transformations inside [builder]. Loading Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +57 −9 Original line number Diff line number Diff line Loading @@ -39,7 +39,6 @@ import androidx.compose.ui.layout.Placeable import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.testTag import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.round Loading Loading @@ -204,6 +203,17 @@ internal class ElementNode( measurable: Measurable, constraints: Constraints, ): MeasureResult { val overscrollScene = layoutImpl.state.currentOverscrollSpec?.scene if (overscrollScene != null && overscrollScene != scene.key) { // There is an overscroll in progress on another scene // By measuring composable elements, Compose can cache relevant information. // This reduces the need for re-measure when users return from an overscroll animation. val placeable = measurable.measure(constraints) return layout(placeable.width, placeable.height) { // We don't want to draw it, no need to place the element. } } val placeable = measure(layoutImpl, scene, element, sceneState, measurable, constraints) return layout(placeable.width, placeable.height) { place(layoutImpl, scene, element, sceneState, placeable, placementScope = this) Loading Loading @@ -253,11 +263,13 @@ private fun shouldDrawElement( ): Boolean { val transition = layoutImpl.state.currentTransition // Always draw the element if there is no ongoing transition or if the element is not shared. // Always draw the element if there is no ongoing transition or if the element is not shared or // if the current scene is the one that is currently over scrolling with [OverscrollSpec]. if ( transition == null || transition.fromScene !in element.sceneStates || transition.toScene !in element.sceneStates transition.toScene !in element.sceneStates || layoutImpl.state.currentOverscrollSpec?.scene == scene.key ) { return true } Loading Loading @@ -286,12 +298,14 @@ internal fun shouldDrawOrComposeSharedElement( val fromScene = transition.fromScene val toScene = transition.toScene return scenePicker.sceneDuringTransition( val chosenByPicker = scenePicker.sceneDuringTransition( element = element, transition = transition, fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex, toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex, ) == scene return chosenByPicker || layoutImpl.state.currentOverscrollSpec?.scene == scene } private fun isSharedElementEnabled( Loading Loading @@ -549,6 +563,40 @@ private inline fun <T> computeValue( return idleValue } if (transition is TransitionState.HasOverscrollProperties) { val overscroll = layoutImpl.state.currentOverscrollSpec if (overscroll?.scene == scene.key) { val elementSpec = overscroll.transformationSpec.transformations(element.key, scene.key) val propertySpec = transformation(elementSpec) ?: return currentValue() val overscrollState = checkNotNull(if (scene.key == toScene) toState else fromState) val targetValue = propertySpec.transform( layoutImpl, scene, element, overscrollState, 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. if (targetValue == idleValue) { return targetValue } // TODO(b/290184746): Make sure that we don't overflow transformations associated to a // range. val directionSign = if (transition.isUpOrLeft) -1 else 1 val overscrollProgress = transition.progress.let { if (it > 1f) it - 1f else it } val progress = directionSign * overscrollProgress val rangeProgress = propertySpec.range?.progress(progress) ?: progress // Interpolate between the value at rest and the over scrolled value. return lerp(idleValue, targetValue, rangeProgress) } } // The element is shared: interpolate between the value in fromScene and the value in toScene. // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +14 −5 Original line number Diff line number Diff line Loading @@ -497,9 +497,11 @@ private class SwipeTransition( val _fromScene: Scene, val _toScene: Scene, private val userActionDistanceScope: UserActionDistanceScope, private val orientation: Orientation, private val isUpOrLeft: Boolean, ) : TransitionState.Transition(_fromScene.key, _toScene.key) { override val orientation: Orientation, override val isUpOrLeft: Boolean, ) : TransitionState.Transition(_fromScene.key, _toScene.key), TransitionState.HasOverscrollProperties { var _currentScene by mutableStateOf(_fromScene) override val currentScene: SceneKey get() = _currentScene.key Loading Loading @@ -789,14 +791,21 @@ internal class SceneNestedScrollHandler( ) fun hasNextScene(amount: Float): Boolean { val fromScene = layoutImpl.scene(layoutState.transitionState.currentScene) val transitionState = layoutState.transitionState val scene = transitionState.currentScene val fromScene = layoutImpl.scene(scene) val nextScene = when { amount < 0f -> fromScene.userActions[actionUpOrLeft] amount > 0f -> fromScene.userActions[actionDownOrRight] else -> null } return nextScene != null if (nextScene != null) return true if (transitionState !is TransitionState.Idle) return false val overscrollSpec = layoutImpl.state.transitions.overscrollSpec(scene, orientation) return overscrollSpec != null } val source = this Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +43 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect Loading Loading @@ -221,6 +222,23 @@ sealed interface TransitionState { isTransitioning(from = other, to = scene) } } interface HasOverscrollProperties { /** * The position of the [TransitionState.Transition.toScene]. * * Used to understand the direction of the overscroll. */ val isUpOrLeft: Boolean /** * The relative orientation between [TransitionState.Transition.fromScene] and * [TransitionState.Transition.toScene]. * * Used to understand the orientation of the overscroll. */ val orientation: Orientation } } internal abstract class BaseSceneTransitionLayoutState( Loading @@ -237,6 +255,25 @@ internal abstract class BaseSceneTransitionLayoutState( */ internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty private var fromOverscrollSpec: OverscrollSpecImpl? = null private var toOverscrollSpec: OverscrollSpecImpl? = null /** * @return the overscroll [OverscrollSpecImpl] if it is defined for the current * [transitionState] and we are currently over scrolling. */ internal val currentOverscrollSpec: OverscrollSpecImpl? get() { val transition = currentTransition ?: return null if (transition !is TransitionState.HasOverscrollProperties) return null val progress = transition.progress return when { progress < 0f -> fromOverscrollSpec progress > 1f -> toOverscrollSpec else -> null } } private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() /** Whether we can transition to the given [scene]. */ Loading Loading @@ -266,10 +303,13 @@ internal abstract class BaseSceneTransitionLayoutState( transitionKey: TransitionKey?, ) { // Compute the [TransformationSpec] when the transition starts. val fromScene = transition.fromScene val toScene = transition.toScene val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation transformationSpec = transitions .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey) .transformationSpec() transitions.transitionSpec(fromScene, toScene, key = transitionKey).transformationSpec() fromOverscrollSpec = orientation?.let { transitions.overscrollSpec(fromScene, it) } toOverscrollSpec = orientation?.let { transitions.overscrollSpec(toScene, it) } cancelActiveTransitionLinks() setupTransitionLinks(transition) transitionState = transition Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +54 −4 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import androidx.compose.animation.core.Spring import androidx.compose.animation.core.SpringSpec import androidx.compose.animation.core.snap import androidx.compose.animation.core.spring import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastForEach Loading @@ -41,18 +42,22 @@ class SceneTransitions internal constructor( internal val defaultSwipeSpec: SpringSpec<Float>, internal val transitionSpecs: List<TransitionSpecImpl>, internal val overscrollSpecs: List<OverscrollSpecImpl>, ) { private val cache = private val transitionCache = mutableMapOf< SceneKey, MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>> >() private val overscrollCache = mutableMapOf<SceneKey, MutableMap<Orientation, OverscrollSpecImpl?>>() internal fun transitionSpec( from: SceneKey, to: SceneKey, key: TransitionKey?, ): TransitionSpecImpl { return cache return transitionCache .getOrPut(from) { mutableMapOf() } .getOrPut(to) { mutableMapOf() } .getOrPut(key) { findSpec(from, to, key) } Loading Loading @@ -105,6 +110,28 @@ internal constructor( private fun defaultTransition(from: SceneKey, to: SceneKey) = TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider) internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? = overscrollCache .getOrPut(scene) { mutableMapOf() } .getOrPut(orientation) { overscroll(scene, orientation) { it.scene == scene } } private fun overscroll( scene: SceneKey, orientation: Orientation, filter: (OverscrollSpecImpl) -> Boolean, ): OverscrollSpecImpl? { var match: OverscrollSpecImpl? = null overscrollSpecs.fastForEach { spec -> if (spec.orientation == orientation && filter(spec)) { if (match != null) { error("Found multiple transition specs for transition $scene") } match = spec } } return match } companion object { internal val DefaultSwipeSpec = spring( Loading @@ -112,7 +139,12 @@ internal constructor( visibilityThreshold = OffsetVisibilityThreshold, ) val Empty = SceneTransitions(DefaultSwipeSpec, transitionSpecs = emptyList()) val Empty = SceneTransitions( defaultSwipeSpec = DefaultSwipeSpec, transitionSpecs = emptyList(), overscrollSpecs = emptyList(), ) } } Loading @@ -139,7 +171,7 @@ interface TransitionSpec { */ fun reversed(): TransitionSpec /* /** * The [TransformationSpec] associated to this [TransitionSpec]. * * Note that this is called once every a transition associated to this [TransitionSpec] is Loading Loading @@ -212,6 +244,24 @@ internal class TransitionSpecImpl( override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke() } /** The definition of the overscroll behavior of the [scene]. */ interface OverscrollSpec { /** The scene we are over scrolling. */ val scene: SceneKey /** The orientation of this [OverscrollSpec]. */ val orientation: Orientation /** The [TransformationSpec] associated to this [OverscrollSpec]. */ val transformationSpec: TransformationSpec } internal class OverscrollSpecImpl( override val scene: SceneKey, override val orientation: Orientation, override val transformationSpec: TransformationSpecImpl, ) : OverscrollSpec /** * An implementation of [TransformationSpec] that allows the quick retrieval of an element * [ElementTransformations]. Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +30 −13 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp Loading Loading @@ -72,24 +73,23 @@ interface SceneTransitionsBuilder { key: TransitionKey? = null, builder: TransitionBuilder.() -> Unit = {}, ): TransitionSpec } @TransitionDsl interface TransitionBuilder : PropertyTransformationBuilder { /** * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when * the transition is triggered (i.e. it is not gesture-based). */ var spec: AnimationSpec<Float> /** * The [SpringSpec] used to animate the associated transition progress when the transition was * started by a swipe and is now animating back to a scene because the user lifted their finger. * Define the animation to be played when the [scene] is overscrolled in the given * [orientation]. * * If `null`, then the [SceneTransitionsBuilder.defaultSwipeSpec] will be used. * The overscroll animation always starts from a progress of 0f, and reaches 1f when moving the * [distance] down/right, -1f when moving in the opposite direction. */ var swipeSpec: SpringSpec<Float>? fun overscroll( scene: SceneKey, orientation: Orientation, builder: OverscrollBuilder.() -> Unit = {}, ): OverscrollSpec } @TransitionDsl interface OverscrollBuilder : PropertyTransformationBuilder { /** * The distance it takes for this transition to animate from 0% to 100% when it is driven by a * [UserAction]. Loading Loading @@ -117,6 +117,23 @@ interface TransitionBuilder : PropertyTransformationBuilder { end: Float? = null, builder: PropertyTransformationBuilder.() -> Unit, ) } @TransitionDsl interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder { /** * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when * the transition is triggered (i.e. it is not gesture-based). */ var spec: AnimationSpec<Float> /** * The [SpringSpec] used to animate the associated transition progress when the transition was * started by a swipe and is now animating back to a scene because the user lifted their finger. * * If `null`, then the [SceneTransitionsBuilder.defaultSwipeSpec] will be used. */ var swipeSpec: SpringSpec<Float>? /** * Define a timestamp-based range for the transformations inside [builder]. Loading