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?>

Segment holds initialState and targetState, which are the beginning and end of a transition.

Each animation created using animateFloat, animateDp, etc is represented as a TransitionAnimationState in Transition.

Public functions

open String
Cmn

Public properties

List<Transition.TransitionAnimationState<*, *, S>>

List of TransitionAnimationStates that are in a Transition.

Cmn
S

Current state of the transition.

Cmn
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.

Cmn
Boolean

Indicates whether there is any animation running in the transition.

Cmn
String?
Cmn
Transition.Segment<S>

segment contains the initial state and the target state of the currently on-going transition.

Cmn
S

Target state of the transition.

Cmn
Long

Total duration of the Transition, accounting for all the animations and child transitions defined on the Transition.

Cmn
List<Transition<*>>

List of child transitions in a Transition.

Cmn

Extension functions

Unit
@Composable
<S : Any?> Transition<S>.AnimatedContent(
    modifier: Modifier,
    transitionSpec: AnimatedContentTransitionScope<S>.() -> ContentTransform,
    contentAlignment: Alignment,
    contentKey: (targetState) -> Any,
    content: @Composable AnimatedContentScope.(targetState) -> Unit
)

AnimatedContent is a container that automatically animates its content when Transition.targetState changes.

Cmn
Unit
@Composable
<T : Any?> Transition<T>.AnimatedVisibility(
    visible: (T) -> Boolean,
    modifier: Modifier,
    enter: EnterTransition,
    exit: ExitTransition,
    content: @Composable AnimatedVisibilityScope.() -> Unit
)

This extension function creates an AnimatedVisibility composable as a child Transition of the given Transition.

Cmn
Unit
@ExperimentalAnimationApi
@Composable
<T : Any?> Transition<T>.Crossfade(
    modifier: Modifier,
    animationSpec: FiniteAnimationSpec<Float>,
    contentKey: (targetState) -> Any,
    content: @Composable (targetState) -> Unit
)

Crossfade allows to switch between two layouts with a crossfade animation.

Cmn
inline State<Color>
@Composable
<S : Any?> Transition<S>.animateColor(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Color>,
    label: String,
    targetValueByState: @Composable (state) -> Color
)

Creates a Color animation as a part of the given Transition.

Cmn
inline State<Dp>
@Composable
<S : Any?> Transition<S>.animateDp(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Dp>,
    label: String,
    targetValueByState: @Composable (state) -> Dp
)

Creates a Dp animation as a part of the given Transition.

Cmn
inline State<Float>
@Composable
<S : Any?> Transition<S>.animateFloat(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Float>,
    label: String,
    targetValueByState: @Composable (state) -> Float
)

Creates a Float animation as a part of the given Transition.

Cmn
inline State<Int>
@Composable
<S : Any?> Transition<S>.animateInt(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Int>,
    label: String,
    targetValueByState: @Composable (state) -> Int
)

Creates a Int animation as a part of the given Transition.

Cmn
inline State<IntOffset>
@Composable
<S : Any?> Transition<S>.animateIntOffset(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<IntOffset>,
    label: String,
    targetValueByState: @Composable (state) -> IntOffset
)

Creates a IntOffset animation as a part of the given Transition.

Cmn
inline State<IntSize>
@Composable
<S : Any?> Transition<S>.animateIntSize(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<IntSize>,
    label: String,
    targetValueByState: @Composable (state) -> IntSize
)

Creates a IntSize animation as a part of the given Transition.

Cmn
inline State<Offset>
@Composable
<S : Any?> Transition<S>.animateOffset(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Offset>,
    label: String,
    targetValueByState: @Composable (state) -> Offset
)

Creates an Offset animation as a part of the given Transition.

Cmn
inline State<Rect>
@Composable
<S : Any?> Transition<S>.animateRect(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Rect>,
    label: String,
    targetValueByState: @Composable (state) -> Rect
)

Creates a Rect animation as a part of the given Transition.

Cmn
inline State<Size>
@Composable
<S : Any?> Transition<S>.animateSize(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Size>,
    label: String,
    targetValueByState: @Composable (state) -> Size
)

Creates a Size animation as a part of the given Transition.

Cmn
inline State<T>
@Composable
<S : Any?, T : Any?, V : AnimationVector> Transition<S>.animateValue(
    typeConverter: TwoWayConverter<T, V>,
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<T>,
    label: String,
    targetValueByState: @Composable (state) -> T
)

Creates an animation of type T as a part of the given Transition.

Cmn
inline Transition<T>
@ExperimentalTransitionApi
@Composable
<S : Any?, T : Any?> Transition<S>.createChildTransition(
    label: String,
    transformToChildState: @Composable (parentState) -> T
)

createChildTransition creates a child Transition based on the mapping between parent state to child state provided in transformToChildState.

Cmn

Public functions

toString

open fun toString(): String

Public properties

animations

val animationsList<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 hasInitialValueAnimationsBoolean

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 isRunningBoolean

Indicates whether there is any animation running in the transition.

label

val labelString?

segment

val segmentTransition.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 totalDurationNanosLong

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.

transitions

val transitionsList<Transition<*>>

List of child transitions in a Transition.

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()
        }
    }
}

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

modifier for the Layout created to contain the content

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 Transition.targetState and the provided visible lambda

@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 AnimationSpec to configure the animation.

@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)
    )
}
Returns
State<Color>

A State object, the value of which is updated by animation

@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.

Returns
State<Dp>

A State object, the value of which is updated by animation

@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.

Returns
State<Float>

A State object, the value of which is updated by animation

@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.

Returns
State<Int>

A State object, the value of which is updated by animation

@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.

Returns
State<IntOffset>

A State object, the value of which is updated by animation

@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.

Returns
State<IntSize>

A State object, the value of which is updated by animation

@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.

Returns
State<Offset>

A State object, the value of which is updated by animation

@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.

Returns
State<Rect>

A State object, the value of which is updated by animation

@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.

Returns
State<Size>

A State object, the value of which is updated by animation

@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.

Returns
State<T>

A State object, the value of which is updated by animation

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:

  1. 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.

  2. 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()
            )
        }
    }
}