Transition
-
Cmn
class Transition<S : Any?>
Transition manages all the child animations on a state level. Child animations can be created in a declarative way using Transition.animateFloat, Transition.animateValue, animateColor etc. When the targetState changes, Transition will automatically start or adjust course for all its child animations to animate to the new target values defined for each animation.
After arriving at targetState, Transition will be triggered to run if any child animation changes its target value (due to their dynamic target calculation logic, such as theme-dependent values).
import androidx.compose.animation.animateColor import androidx.compose.animation.core.Transition import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp // enum class ComponentState { Pressed, Released } var useRed by remember { mutableStateOf(false) } var toState by remember { mutableStateOf(ComponentState.Released) } val modifier = Modifier.pointerInput(Unit) { detectTapGestures( onPress = { toState = ComponentState.Pressed tryAwaitRelease() toState = ComponentState.Released } ) } // Defines a transition of `ComponentState`, and updates the transition when the provided // [targetState] changes. The transition will run all of the child animations towards the new // [targetState] in response to the [targetState] change. val transition: Transition<ComponentState> = updateTransition(targetState = toState) // Defines a float animation as a child animation the transition. The current animation value // can be read from the returned State<Float>. val scale: Float by transition.animateFloat( // Defines a transition spec that uses the same low-stiffness spring for *all* // transitions of this float, no matter what the target is. transitionSpec = { spring(stiffness = 50f) } ) { state -> // This code block declares a mapping from state to value. if (state == ComponentState.Pressed) 3f else 1f } // Defines a color animation as a child animation of the transition. val color: Color by transition.animateColor( transitionSpec = { when { ComponentState.Pressed isTransitioningTo ComponentState.Released -> // Uses spring for the transition going from pressed to released spring(stiffness = 50f) else -> // Uses tween for all the other transitions. (In this case there is // only one other transition. i.e. released -> pressed.) tween(durationMillis = 500) } } ) { state -> when (state) { // Similar to the float animation, we need to declare the target values // for each state. In this code block we can access theme colors. ComponentState.Pressed -> MaterialTheme.colors.primary // We can also have the target value depend on other mutableStates, // such as `useRed` here. Whenever the target value changes, transition // will automatically animate to the new value even if it has already // arrived at its target state. ComponentState.Released -> if (useRed) Color.Red else MaterialTheme.colors.secondary } } Column { Button( modifier = Modifier.padding(10.dp).align(Alignment.CenterHorizontally), onClick = { useRed = !useRed }, ) { Text("Change Color") } Box( modifier .fillMaxSize() .wrapContentSize(Alignment.Center) .size((100 * scale).dp) .background(color) ) }
Summary
Nested types |
|---|
interface Transition.Segment<S : Any?>
|
inner class Transition.TransitionAnimationState<T : Any?, V : AnimationVector> : StateEach animation created using |
Public properties |
||
|---|---|---|
List<Transition.TransitionAnimationState<*, *, S>> |
List of |
Cmn
|
S |
Current state of the transition. |
Cmn
|
Boolean |
Used internally to know when a |
Cmn
|
Boolean |
Indicates whether there is any animation running in the transition. |
Cmn
|
String? |
Cmn
|
|
Transition.Segment<S> |
|
Cmn
|
S |
Target state of the transition. |
Cmn
|
Long |
Total duration of the |
Cmn
|
List<Transition<*>> |
List of child transitions in a |
Cmn
|
Extension functions |
||
|---|---|---|
Unit |
@Composable
|
Cmn
|
Unit |
@ComposableThis extension function creates an |
Cmn
|
Unit |
@ExperimentalAnimationApi
|
Cmn
|
inline State<Color> |
@ComposableCreates a |
Cmn
|
inline State<Dp> |
@ComposableCreates a |
Cmn
|
inline State<Float> |
@ComposableCreates a Float animation as a part of the given |
Cmn
|
inline State<Int> |
@ComposableCreates a |
Cmn
|
inline State<IntOffset> |
@ComposableCreates a |
Cmn
|
inline State<IntSize> |
@ComposableCreates a |
Cmn
|
inline State<Offset> |
@ComposableCreates an |
Cmn
|
inline State<Rect> |
@ComposableCreates a |
Cmn
|
inline State<Size> |
@ComposableCreates a |
Cmn
|
inline State<T> |
@ComposableCreates an animation of type |
Cmn
|
inline Transition<T> |
@ExperimentalTransitionApi
|
Cmn
|
Public properties
animations
val animations: List<Transition.TransitionAnimationState<*, *, S>>
List of TransitionAnimationStates that are in a Transition.
currentState
val currentState: S
Current state of the transition. This will always be the initialState of the transition until the transition is finished. Once the transition is finished, currentState will be set to targetState. currentState is backed by a MutableState.
hasInitialValueAnimations
@InternalAnimationApi
val hasInitialValueAnimations: Boolean
Used internally to know when a SeekableTransitionState is animating initial values after SeekableTransitionState.animateTo or SeekableTransitionState.seekTo has redirected a transition prior to it completing. This is important for knowing when child transitions must be maintained after a parent target state has changed, but the child target state hasn't changed.
isRunning
val isRunning: Boolean
Indicates whether there is any animation running in the transition.
segment
val segment: Transition.Segment<S>
segment contains the initial state and the target state of the currently on-going transition.
targetState
val targetState: S
Target state of the transition. This will be read by all child animations to determine their most up-to-date target values.
totalDurationNanos
val totalDurationNanos: Long
Total duration of the Transition, accounting for all the animations and child transitions defined on the Transition.
Note: The total duration is subject to change as more animations/child transitions get added to Transition. It's strongly recommended to query this after all the animations in the Transition are set up.
Extension functions
AnimatedContent
@Composable
fun <S : Any?> Transition<S>.AnimatedContent(
modifier: Modifier = Modifier,
transitionSpec: AnimatedContentTransitionScope<S>.() -> ContentTransform = { (fadeIn(animationSpec = tween(220, delayMillis = 90)) + scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90))) .togetherWith(fadeOut(animationSpec = tween(90))) },
contentAlignment: Alignment = Alignment.TopStart,
contentKey: (targetState) -> Any? = { it },
content: @Composable AnimatedContentScope.(targetState) -> Unit
): Unit
AnimatedContent is a container that automatically animates its content when Transition.targetState changes. Its content for different target states is defined in a mapping between a target state and a composable function.
IMPORTANT: The targetState parameter for the content lambda should always be taken into account in deciding what composable function to return as the content for that state. This is critical to ensure a successful lookup of all the incoming and outgoing content during content transform.
When Transition.targetState changes, content for both new and previous targetState will be looked up through the content lambda. They will go through a ContentTransform so that the new target content can be animated in while the initial content animates out. Meanwhile the container will animate its size as needed to accommodate the new content, unless SizeTransform is set to null. Once the ContentTransform is finished, the outgoing content will be disposed.
If Transition.targetState is expected to mutate frequently and not all mutations should be treated as target state change, consider defining a mapping between Transition.targetState and a key in contentKey. As a result, transitions will be triggered when the resulting key changes. In other words, there will be no animation when switching between Transition.targetStates that share the same key. By default, the key will be the same as the targetState object.
By default, the ContentTransform will be a delayed fadeIn of the target content and a delayed scaleIn a fadeOut of the initial content, using a SizeTransform to animate any size change of the content. This behavior can be customized using transitionSpec. If desired, different ContentTransforms can be defined for different pairs of initial content and target content.
AnimatedContent displays only the content for Transition.targetState when not animating. However, during the transient content transform, there will be more than one sets of content present in the AnimatedContent container. It may be sometimes desired to define the positional relationship among different content and the style of overlap. This can be achieved by defining contentAlignment and zOrder. By default, contentAlignment aligns all content to Alignment.TopStart, and the zIndex for all the content is 0f. Note: The target content will always be placed last, therefore it will be on top of all the other content unless zIndex is specified.
Different content in AnimatedContent will have access to their own AnimatedContentScope. This allows content to define more local enter/exit transitions via AnimatedContentScope.animateEnterExit and AnimatedContentScope.transition. These custom enter/exit animations will be triggered as the content enters/leaves the container.
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.SizeTransform import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.keyframes import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp @Composable fun CollapsedCart() { /* Some content here */ } @Composable fun ExpandedCart() { /* Some content here */ } // enum class CartState { Expanded, Collapsed } var cartState by remember { mutableStateOf(CartState.Collapsed) } // Creates a transition here to animate the corner shape and content. val cartOpenTransition = updateTransition(cartState, "CartOpenTransition") val cornerSize by cartOpenTransition.animateDp( label = "cartCornerSize", transitionSpec = { when { CartState.Expanded isTransitioningTo CartState.Collapsed -> tween(durationMillis = 433, delayMillis = 67) else -> tween(durationMillis = 150) } }, ) { if (it == CartState.Expanded) 0.dp else 24.dp } Surface( Modifier.shadow(8.dp, CutCornerShape(topStart = cornerSize)) .clip(CutCornerShape(topStart = cornerSize)), color = Color(0xfffff0ea), ) { // Creates an AnimatedContent using the transition. This AnimatedContent will // derive its target state from cartOpenTransition.targetState. All the animations // created inside of AnimatedContent for size change, enter/exit will be added to the // Transition. cartOpenTransition.AnimatedContent( transitionSpec = { fadeIn(animationSpec = tween(150, delayMillis = 150)) .togetherWith(fadeOut(animationSpec = tween(150))) .using( SizeTransform { initialSize, targetSize -> // Using different SizeTransform for different state change if (CartState.Collapsed isTransitioningTo CartState.Expanded) { keyframes { durationMillis = 500 // Animate to full target width and by 200px in height at 150ms IntSize(targetSize.width, initialSize.height + 200) at 150 } } else { keyframes { durationMillis = 500 // Animate 1/2 the height without changing the width at 150ms. // The width and rest of the height will be animated in the // timeframe between 150ms and duration (i.e. 500ms) IntSize( initialSize.width, (initialSize.height + targetSize.height) / 2, ) at 150 } } } ) .apply { targetContentZIndex = when (targetState) { // This defines a relationship along z-axis during the momentary // overlap as both incoming and outgoing content is on screen. This // fixed zOrder will ensure that collapsed content will always be on // top of the expanded content - it will come in on top, and // disappear over the expanded content as well. CartState.Expanded -> 1f CartState.Collapsed -> 2f } } } ) { // This defines the mapping from state to composable. It's critical to use the state // parameter (i.e. `it`) that is passed into this block of code to ensure correct // content lookup. when (it) { CartState.Expanded -> ExpandedCart() CartState.Collapsed -> CollapsedCart() } } }
| See also | |
|---|---|
ContentTransform |
|
AnimatedContentScope |
AnimatedVisibility
@Composable
fun <T : Any?> Transition<T>.AnimatedVisibility(
visible: (T) -> Boolean,
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandIn(),
exit: ExitTransition = shrinkOut() + fadeOut(),
content: @Composable AnimatedVisibilityScope.() -> Unit
): Unit
This extension function creates an AnimatedVisibility composable as a child Transition of the given Transition. This means: 1) the enter/exit transition is now triggered by the provided Transition's targetState change. When the targetState changes, the visibility will be derived using the visible lambda and Transition.targetState. 2) The enter/exit transitions, as well as any custom enter/exit animations defined in AnimatedVisibility are now hoisted to the parent Transition. The parent Transition will wait for all of them to finish before it considers itself finished (i.e. Transition.currentState = Transition.targetState), and subsequently removes the content in the exit case.
Different EnterTransitions and ExitTransitions can be defined in enter and exit for the appearance and disappearance animation. There are 4 types of EnterTransition and ExitTransition: Fade, Expand/Shrink, Scale and Slide. The enter transitions can be combined using +. Same for exit transitions. The order of the combination does not matter, as the transition animations will start simultaneously. See EnterTransition and ExitTransition for details on the three types of transition.
Aside from these three types of EnterTransition and ExitTransition, AnimatedVisibility also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit animations on shape, scale, color, etc. Custom enter/exit animations can be created using the Transition<EnterExitState> object from the AnimatedVisibilityScope (i.e. AnimatedVisibilityScope.transition). See EnterExitState for an example of custom animations. These custom animations will be running along side of the built-in animations specified in enter and exit. In cases where the enter/exit animation needs to be completely customized, enter and/or exit can be specified as EnterTransition.None and/or ExitTransition.None as needed. AnimatedVisibility will wait until all of enter/exit animations to finish before it considers itself idle. content will only be removed after all the (built-in and custom) exit animations have finished.
AnimatedVisibility creates a custom Layout for its content. The size of the custom layout is determined by the largest width and largest height of the children. All children will be aligned to the top start of the Layout.
Note: Once the exit transition is finished, the content composable will be removed from the tree, and disposed.
By default, the enter transition will be a combination of fadeIn and expandIn of the content from the bottom end. And the exit transition will be shrinking the content towards the bottom end while fading out (i.e. fadeOut + shrinkOut). The expanding and shrinking will likely also animate the parent and siblings if they rely on the size of appearing/disappearing content.
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.animateColor import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @Composable fun ItemMainContent() { Row(Modifier.height(100.dp).fillMaxWidth(), Arrangement.SpaceEvenly) { Box( Modifier.size(60.dp) .align(Alignment.CenterVertically) .background(Color(0xffcdb7f6), CircleShape) ) Column(Modifier.align(Alignment.CenterVertically)) { Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray)) Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray)) } } } @OptIn(ExperimentalAnimationApi::class) @Composable fun SelectableItem() { // This sample animates a number of properties, including AnimatedVisibility, as a part of // the Transition going between selected and unselected. Box(Modifier.padding(15.dp)) { var selected by remember { mutableStateOf(false) } // Creates a transition to animate visual changes when `selected` is changed. val selectionTransition = updateTransition(selected) // Animates the border color as a part of the transition val borderColor by selectionTransition.animateColor { isSelected -> if (isSelected) Color(0xff03a9f4) else Color.White } // Animates the background color when selected state changes val contentBackground by selectionTransition.animateColor { isSelected -> if (isSelected) Color(0xffdbf0fe) else Color.White } // Animates elevation as a part of the transition val elevation by selectionTransition.animateDp { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( shape = RoundedCornerShape(10.dp), border = BorderStroke(2.dp, borderColor), modifier = Modifier.clickable { selected = !selected }, color = contentBackground, elevation = elevation, ) { Column(Modifier.fillMaxWidth()) { ItemMainContent() // Creates an AnimatedVisibility as a part of the transition, so that when // selected it's visible. This will hoist all the animations that are internal // to AnimatedVisibility (i.e. fade, slide, etc) to the transition. As a result, // `selectionTransition` will not finish until all the animations in // AnimatedVisibility as well as animations added directly to it have finished. selectionTransition.AnimatedVisibility( visible = { it }, enter = expandVertically(), exit = shrinkVertically(), ) { Box(Modifier.fillMaxWidth().padding(10.dp)) { Text( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed" + " eiusmod tempor incididunt labore et dolore magna aliqua. " + "Ut enim ad minim veniam, quis nostrud exercitation ullamco " + "laboris nisi ut aliquip ex ea commodo consequat. Duis aute " + "irure dolor." ) } } } } } }
| Parameters | |
|---|---|
visible: (T) -> Boolean |
defines whether the content should be visible based on transition state T |
modifier: Modifier = Modifier |
|
enter: EnterTransition = fadeIn() + expandIn() |
EnterTransition(s) used for the appearing animation, fading in while expanding vertically by default |
exit: ExitTransition = shrinkOut() + fadeOut() |
ExitTransition(s) used for the disappearing animation, fading out while shrinking vertically by default |
content: @Composable AnimatedVisibilityScope.() -> Unit |
Content to appear or disappear based on the visibility derived from the |
Crossfade
@ExperimentalAnimationApi
@Composable
fun <T : Any?> Transition<T>.Crossfade(
modifier: Modifier = Modifier,
animationSpec: FiniteAnimationSpec<Float> = tween(),
contentKey: (targetState) -> Any? = { it },
content: @Composable (targetState) -> Unit
): Unit
Crossfade allows to switch between two layouts with a crossfade animation. The target state of this Crossfade will be the target state of the given Transition object. In other words, when the Transition changes target, the Crossfade will fade in the target content while fading out the current content.
content is a mapping between the state and the composable function for the content of that state. During the crossfade, content lambda will be invoked multiple times with different state parameter such that content associated with different states will be fading in/out at the same time.
contentKey will be used to perform equality check for different states. For example, when two states resolve to the same content key, there will be no animation for that state change. By default, contentKey is the same as the state object. contentKey can be particularly useful if target state object gets recreated across save & restore while a more persistent key is needed to properly restore the internal states of the content.
| Parameters | |
|---|---|
modifier: Modifier = Modifier |
Modifier to be applied to the animation container. |
animationSpec: FiniteAnimationSpec<Float> = tween() |
the |
contentKey: (targetState) -> Any? = { it } |
A mapping from a given state to an object of |
content: @Composable (targetState) -> Unit |
A mapping from a given state to the content corresponding to that state. |
animateColor
@Composable
inline fun <S : Any?> Transition<S>.animateColor(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Color> = { spring() },
label: String = "ColorAnimation",
targetValueByState: @Composable (state) -> Color
): State<Color>
Creates a Color animation as a part of the given Transition. This means the lifecycle of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the target value changes when the Transition already reached its targetState, the Transition will run an animation to ensure the new target value is reached smoothly.
An optional transitionSpec can be provided to specify (potentially different) animations for each pair of initialState and targetState. FiniteAnimationSpec can be used to describe such animations, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
import androidx.compose.animation.animateColor import androidx.compose.animation.core.Transition import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp // enum class ComponentState { Pressed, Released } var useRed by remember { mutableStateOf(false) } var toState by remember { mutableStateOf(ComponentState.Released) } val modifier = Modifier.pointerInput(Unit) { detectTapGestures( onPress = { toState = ComponentState.Pressed tryAwaitRelease() toState = ComponentState.Released } ) } // Defines a transition of `ComponentState`, and updates the transition when the provided // [targetState] changes. The tran // sition will run all of the child animations towards the new // [targetState] in response to the [targetState] change. val transition: Transition<ComponentState> = updateTransition(targetState = toState) // Defines a float animation as a child animation the transition. The current animation value // can be read from the returned State<Float>. val scale: Float by transition.animateFloat( // Defines a transition spec that uses the same low-stiffness spring for *all* // transitions of this float, no matter what the target is. transitionSpec = { spring(stiffness = 50f) } ) { state -> // This code block declares a mapping from state to value. if (state == ComponentState.Pressed) 3f else 1f } // Defines a color animation as a child animation of the transition. val color: Color by transition.animateColor( transitionSpec = { when { ComponentState.Pressed isTransitioningTo ComponentState.Released -> // Uses spring for the transition going from pressed to released spring(stiffness = 50f) else -> // Uses tween for all the other transitions. (In this case there is // only one other transition. i.e. released -> pressed.) tween(durationMillis = 500) } } ) { state -> when (state) { // Similar to the float animation, we need to declare the target values // for each state. In this code block we can access theme colors. ComponentState.Pressed -> MaterialTheme.colors.primary // We can also have the target value depend on other mutableStates, // such as `useRed` here. Whenever the target value changes, transition // will automatically animate to the new value even if it has already // arrived at its target state. ComponentState.Released -> if (useRed) Color.Red else MaterialTheme.colors.secondary } } Column { Button( modifier = Modifier.padding(10.dp).align(Alignment.CenterHorizontally), onClick = { useRed = !useRed }, ) { Text("Change Color") } Box( modifier .fillMaxSize() .wrapContentSize(Alignment.Center) .size((100 * scale).dp) .background(color) ) }
| See also | |
|---|---|
animateValue |
|
Transition |
|
updateTransition |
animateDp
@Composable
inline fun <S : Any?> Transition<S>.animateDp(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Dp> = { spring(visibilityThreshold = Dp.VisibilityThreshold) },
label: String = "DpAnimation",
targetValueByState: @Composable (state) -> Dp
): State<Dp>
Creates a Dp animation as a part of the given Transition. This means the states of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
animateFloat
@Composable
inline fun <S : Any?> Transition<S>.animateFloat(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Float> = { spring() },
label: String = "FloatAnimation",
targetValueByState: @Composable (state) -> Float
): State<Float>
Creates a Float animation as a part of the given Transition. This means the states of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
import androidx.compose.animation.core.Transition import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.keyframes import androidx.compose.animation.core.snap import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.material.Button import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.graphicsLayer // enum class ButtonStatus {Initial, Pressed, Released} @Composable fun AnimateAlphaAndScale(modifier: Modifier, transition: Transition<ButtonStatus>) { // Defines a float animation as a child animation of transition. This allows the // transition to manage the states of this animation. The returned State<Float> from the // [animateFloat] function is used here as a property delegate. // This float animation will use the default [spring] for all transition destinations, as // specified by the default `transitionSpec`. val scale: Float by transition.animateFloat { state -> if (state == ButtonStatus.Pressed) 1.2f else 1f } // Alternatively, we can specify different animation specs based on the initial state and // target state of the a transition run using `transitionSpec`. val alpha: Float by transition.animateFloat( transitionSpec = { when { ButtonStatus.Initial isTransitioningTo ButtonStatus.Pressed -> { keyframes { durationMillis = 225 0f at 0 // optional 0.3f at 75 0.2f at 225 // optional } } ButtonStatus.Pressed isTransitioningTo ButtonStatus.Released -> { tween(durationMillis = 220) } else -> { snap() } } } ) { state -> // Same target value for Initial and Released states if (state == ButtonStatus.Pressed) 0.2f else 0f } Box(modifier.graphicsLayer(alpha = alpha, scaleX = scale)) { // content goes here } }
label is used to differentiate from other animations in the same transition in Android Studio.
animateInt
@Composable
inline fun <S : Any?> Transition<S>.animateInt(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Int> = { spring(visibilityThreshold = 1) },
label: String = "IntAnimation",
targetValueByState: @Composable (state) -> Int
): State<Int>
Creates a Int animation as a part of the given Transition. This means the states of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
animateIntOffset
@Composable
inline fun <S : Any?> Transition<S>.animateIntOffset(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<IntOffset> = { spring(visibilityThreshold = IntOffset(1, 1)) },
label: String = "IntOffsetAnimation",
targetValueByState: @Composable (state) -> IntOffset
): State<IntOffset>
Creates a IntOffset animation as a part of the given Transition. This means the states of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
animateIntSize
@Composable
inline fun <S : Any?> Transition<S>.animateIntSize(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<IntSize> = { spring(visibilityThreshold = IntSize(1, 1)) },
label: String = "IntSizeAnimation",
targetValueByState: @Composable (state) -> IntSize
): State<IntSize>
Creates a IntSize animation as a part of the given Transition. This means the states of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
animateOffset
@Composable
inline fun <S : Any?> Transition<S>.animateOffset(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Offset> = { spring(visibilityThreshold = Offset.VisibilityThreshold) },
label: String = "OffsetAnimation",
targetValueByState: @Composable (state) -> Offset
): State<Offset>
Creates an Offset animation as a part of the given Transition. This means the states of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
animateRect
@Composable
inline fun <S : Any?> Transition<S>.animateRect(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Rect> = { spring(visibilityThreshold = Rect.VisibilityThreshold) },
label: String = "RectAnimation",
targetValueByState: @Composable (state) -> Rect
): State<Rect>
Creates a Rect animation as a part of the given Transition. This means the states of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
animateSize
@Composable
inline fun <S : Any?> Transition<S>.animateSize(
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Size> = { spring(visibilityThreshold = Size.VisibilityThreshold) },
label: String = "SizeAnimation",
targetValueByState: @Composable (state) -> Size
): State<Size>
Creates a Size animation as a part of the given Transition. This means the states of this animation will be managed by the Transition.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
animateValue
@Composable
inline fun <S : Any?, T : Any?, V : AnimationVector> Transition<S>.animateValue(
typeConverter: TwoWayConverter<T, V>,
noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<T> = { spring() },
label: String = "ValueAnimation",
targetValueByState: @Composable (state) -> T
): State<T>
Creates an animation of type T as a part of the given Transition. This means the states of this animation will be managed by the Transition. typeConverter will be used to convert between type T and AnimationVector so that the animation system knows how to animate it.
targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes outside of a Transition run (i.e. when the Transition already reached its targetState), the Transition will start running again to ensure this animation reaches its new target smoothly.
An optional transitionSpec can be provided to specify (potentially different) animation for each pair of initialState and targetState. FiniteAnimationSpec includes any non-infinite animation, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.
label is used to differentiate from other animations in the same transition in Android Studio.
createChildTransition
@ExperimentalTransitionApi
@Composable
inline fun <S : Any?, T : Any?> Transition<S>.createChildTransition(
label: String = "ChildTransition",
transformToChildState: @Composable (parentState) -> T
): Transition<T>
createChildTransition creates a child Transition based on the mapping between parent state to child state provided in transformToChildState. This serves the following purposes:
-
Hoist the child transition state into parent transition. Therefore the parent Transition will be aware of whether there's any on-going animation due to the same target state change. This will further allow sequential animation to be set up when all animations have finished.
-
Separation of concerns. The child transition can respresent a much more simplified state transition when, for example, mapping from an enum parent state to a Boolean visible state for passing further down the compose tree. The child composables hence can be designed around handling a more simple and a more relevant state change.
label is used to differentiate from other animations in the same transition in Android Studio.
import androidx.compose.animation.core.ExperimentalTransitionApi import androidx.compose.animation.core.Transition import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.createChildTransition import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp // enum class DialerState { DialerMinimized, NumberPad } @OptIn(ExperimentalTransitionApi::class) @Composable fun DialerButton(visibilityTransition: Transition<Boolean>, modifier: Modifier) { val scale by visibilityTransition.animateFloat { visible -> if (visible) 1f else 2f } Box(modifier.scale(scale).background(Color.Black)) { // Content goes here } } @Composable fun NumberPad(visibilityTransition: Transition<Boolean>) { // Create animations using the provided Transition for visibility change here... } @OptIn(ExperimentalTransitionApi::class) @Composable fun childTransitionSample() { var dialerState by remember { mutableStateOf(DialerState.NumberPad) } Box(Modifier.fillMaxSize()) { val parentTransition = updateTransition(dialerState) // Animate to different corner radius based on target state val cornerRadius by parentTransition.animateDp { if (it == DialerState.NumberPad) 0.dp else 20.dp } Box( Modifier.align(Alignment.BottomCenter) .widthIn(50.dp) .heightIn(50.dp) .clip(RoundedCornerShape(cornerRadius)) ) { NumberPad( // Creates a child transition that derives its target state from the parent // transition, and the mapping from parent state to child state. // This will allow: // 1) Parent transition to account for additional animations in the child // Transitions before it considers itself finished. This is useful when you // have a subsequent action after all animations triggered by a state change // have finished. // 2) Separation of concerns. This allows the child composable (i.e. // NumberPad) to only care about its own visibility, rather than knowing about // DialerState. visibilityTransition = parentTransition.createChildTransition { // This is the lambda that defines how the parent target state maps to // child target state. it == DialerState.NumberPad } // Note: If it's not important for the animations within the child composable to // be observable, it's perfectly valid to not hoist the animations through // a Transition object and instead use animate*AsState. ) DialerButton( visibilityTransition = parentTransition.createChildTransition { it == DialerState.DialerMinimized }, modifier = Modifier.matchParentSize(), ) } } }