{% setvar book_path %}/reference/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

Transition

public final class Transition<S extends Object>


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 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
updateTransition
animateFloat
animateValue
animateColor

Summary

Nested types

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

public final inner class Transition.TransitionAnimationState<T extends Object, V extends AnimationVector> implements State

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

Public methods

final @NonNull List<@NonNull Transition.TransitionAnimationState<@NonNull ?, @NonNull ?, @NonNull S>>

List of TransitionAnimationStates that are in a Transition.

final @NonNull S

Current state of the transition.

final String
final @NonNull Transition.Segment<@NonNull S>

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

final @NonNull S

Target state of the transition.

final long

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

final @NonNull List<@NonNull Transition<@NonNull ?>>

List of child transitions in a Transition.

final boolean

Indicates whether there is any animation running in the transition.

Extension functions

final void
@Composable
<S extends Object> AnimatedContentKt.AnimatedContent(
    @NonNull Transition<@NonNull S> receiver,
    @NonNull Modifier modifier,
    @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedContentTransitionScope<@NonNull S>, @NonNull ContentTransform> transitionSpec,
    @NonNull Alignment contentAlignment,
    @NonNull Function1<@NonNull targetState, Object> contentKey,
    @Composable @ExtensionFunctionType @NonNull Function2<@NonNull AnimatedContentScope, @NonNull targetState, Unit> content
)

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

final void
@ExperimentalAnimationApi
@Composable
<T extends Object> AnimatedVisibilityKt.AnimatedVisibility(
    @NonNull Transition<@NonNull T> receiver,
    @NonNull Function1<@NonNull T, @NonNull Boolean> visible,
    @NonNull Modifier modifier,
    @NonNull EnterTransition enter,
    @NonNull ExitTransition exit,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedVisibilityScopeUnit> content
)

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

final void
@ExperimentalAnimationApi
@Composable
<T extends Object> CrossfadeKt.Crossfade(
    @NonNull Transition<@NonNull T> receiver,
    @NonNull Modifier modifier,
    @NonNull FiniteAnimationSpec<@NonNull Float> animationSpec,
    @NonNull Function1<@NonNull targetState, Object> contentKey,
    @Composable @NonNull Function1<@NonNull targetState, Unit> content
)

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

final @NonNull State<@NonNull Color>
@Composable
<S extends Object> TransitionKt.animateColor(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Color>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Color> targetValueByState
)

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

final @NonNull State<@NonNull Dp>
@Composable
<S extends Object> TransitionKt.animateDp(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Dp>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Dp> targetValueByState
)

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

final @NonNull State<@NonNull Float>
@Composable
<S extends Object> TransitionKt.animateFloat(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Float>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Float> targetValueByState
)

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

final @NonNull State<@NonNull Integer>
@Composable
<S extends Object> TransitionKt.animateInt(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Integer>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Integer> targetValueByState
)

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

final @NonNull State<@NonNull IntOffset>
@Composable
<S extends Object> TransitionKt.animateIntOffset(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull IntOffset>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull IntOffset> targetValueByState
)

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

final @NonNull State<@NonNull IntSize>
@Composable
<S extends Object> TransitionKt.animateIntSize(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull IntSize>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull IntSize> targetValueByState
)

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

final @NonNull State<@NonNull Offset>
@Composable
<S extends Object> TransitionKt.animateOffset(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Offset>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Offset> targetValueByState
)

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

final @NonNull State<@NonNull Rect>
@Composable
<S extends Object> TransitionKt.animateRect(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Rect>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Rect> targetValueByState
)

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

final @NonNull State<@NonNull Size>
@Composable
<S extends Object> TransitionKt.animateSize(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Size>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Size> targetValueByState
)

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

final @NonNull State<@NonNull T>
@Composable
<S extends Object, T extends Object, V extends AnimationVector> TransitionKt.animateValue(
    @NonNull Transition<@NonNull S> receiver,
    @NonNull TwoWayConverter<@NonNull T, @NonNull V> typeConverter,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull T>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull T> targetValueByState
)

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

final @NonNull Transition<@NonNull T>
@ExperimentalTransitionApi
@Composable
<S extends Object, T extends Object> TransitionKt.createChildTransition(
    @NonNull Transition<@NonNull S> receiver,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull parentState, @NonNull T> transformToChildState
)

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

Public methods

getAnimations

public final @NonNull List<@NonNull Transition.TransitionAnimationState<@NonNull ?, @NonNull ?, @NonNull S>> getAnimations()

List of TransitionAnimationStates that are in a Transition.

getCurrentState

public final @NonNullgetCurrentState()

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.

getLabel

public final String getLabel()

getSegment

public final @NonNull Transition.Segment<@NonNull S> getSegment()

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

getTargetState

public final @NonNullgetTargetState()

Target state of the transition. This will be read by all child animations to determine their most up-to-date target values.

getTotalDurationNanos

public final long getTotalDurationNanos()

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.

getTransitions

public final @NonNull List<@NonNull Transition<@NonNull ?>> getTransitions()

List of child transitions in a Transition.

isRunning

public final boolean isRunning()

Indicates whether there is any animation running in the transition.

Extension functions

AnimatedContentKt.AnimatedContent

@Composable
public final void <S extends Object> AnimatedContentKt.AnimatedContent(
    @NonNull Transition<@NonNull S> receiver,
    @NonNull Modifier modifier,
    @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedContentTransitionScope<@NonNull S>, @NonNull ContentTransform> transitionSpec,
    @NonNull Alignment contentAlignment,
    @NonNull Function1<@NonNull targetState, Object> contentKey,
    @Composable @ExtensionFunctionType @NonNull Function2<@NonNull AnimatedContentScope, @NonNull targetState, Unit> content
)

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

AnimatedVisibilityKt.AnimatedVisibility

@ExperimentalAnimationApi
@Composable
public final void <T extends Object> AnimatedVisibilityKt.AnimatedVisibility(
    @NonNull Transition<@NonNull T> receiver,
    @NonNull Function1<@NonNull T, @NonNull Boolean> visible,
    @NonNull Modifier modifier,
    @NonNull EnterTransition enter,
    @NonNull ExitTransition exit,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedVisibilityScopeUnit> content
)

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
@NonNull Function1<@NonNull T, @NonNull Boolean> visible

defines whether the content should be visible based on transition state T

@NonNull Modifier modifier

modifier for the Layout created to contain the content

@NonNull EnterTransition enter

EnterTransition(s) used for the appearing animation, fading in while expanding vertically by default

@NonNull ExitTransition exit

ExitTransition(s) used for the disappearing animation, fading out while shrinking vertically by default

@Composable @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedVisibilityScopeUnit> content

Content to appear or disappear based on the visibility derived from the Transition.targetState and the provided visible lambda

CrossfadeKt.Crossfade

@ExperimentalAnimationApi
@Composable
public final void <T extends Object> CrossfadeKt.Crossfade(
    @NonNull Transition<@NonNull T> receiver,
    @NonNull Modifier modifier,
    @NonNull FiniteAnimationSpec<@NonNull Float> animationSpec,
    @NonNull Function1<@NonNull targetState, Object> contentKey,
    @Composable @NonNull Function1<@NonNull targetState, Unit> content
)

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
@NonNull Modifier modifier

Modifier to be applied to the animation container.

@NonNull FiniteAnimationSpec<@NonNull Float> animationSpec

the AnimationSpec to configure the animation.

TransitionKt.animateColor

@Composable
public final @NonNull State<@NonNull Color> <S extends Object> TransitionKt.animateColor(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Color>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Color> targetValueByState
)

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
@NonNull State<@NonNull Color>

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

TransitionKt.animateDp

@Composable
public final @NonNull State<@NonNull Dp> <S extends Object> TransitionKt.animateDp(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Dp>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Dp> targetValueByState
)

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
@NonNull State<@NonNull Dp>

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

TransitionKt.animateFloat

@Composable
public final @NonNull State<@NonNull Float> <S extends Object> TransitionKt.animateFloat(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Float>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Float> targetValueByState
)

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
@NonNull State<@NonNull Float>

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

TransitionKt.animateInt

@Composable
public final @NonNull State<@NonNull Integer> <S extends Object> TransitionKt.animateInt(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Integer>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Integer> targetValueByState
)

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
@NonNull State<@NonNull Integer>

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

TransitionKt.animateIntOffset

@Composable
public final @NonNull State<@NonNull IntOffset> <S extends Object> TransitionKt.animateIntOffset(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull IntOffset>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull IntOffset> targetValueByState
)

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
@NonNull State<@NonNull IntOffset>

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

TransitionKt.animateIntSize

@Composable
public final @NonNull State<@NonNull IntSize> <S extends Object> TransitionKt.animateIntSize(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull IntSize>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull IntSize> targetValueByState
)

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
@NonNull State<@NonNull IntSize>

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

TransitionKt.animateOffset

@Composable
public final @NonNull State<@NonNull Offset> <S extends Object> TransitionKt.animateOffset(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Offset>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Offset> targetValueByState
)

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
@NonNull State<@NonNull Offset>

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

TransitionKt.animateRect

@Composable
public final @NonNull State<@NonNull Rect> <S extends Object> TransitionKt.animateRect(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Rect>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Rect> targetValueByState
)

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
@NonNull State<@NonNull Rect>

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

TransitionKt.animateSize

@Composable
public final @NonNull State<@NonNull Size> <S extends Object> TransitionKt.animateSize(
    @NonNull Transition<@NonNull S> receiver,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull Size>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull Size> targetValueByState
)

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
@NonNull State<@NonNull Size>

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

TransitionKt.animateValue

@Composable
public final @NonNull State<@NonNull T> <S extends Object, T extends Object, V extends AnimationVector> TransitionKt.animateValue(
    @NonNull Transition<@NonNull S> receiver,
    @NonNull TwoWayConverter<@NonNull T, @NonNull V> typeConverter,
    @Composable @ExtensionFunctionType @NonNull Function1<@NonNull Transition.Segment<@NonNull S>, @NonNull FiniteAnimationSpec<@NonNull T>> transitionSpec,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull state, @NonNull T> targetValueByState
)

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
@NonNull State<@NonNull T>

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

TransitionKt.createChildTransition

@ExperimentalTransitionApi
@Composable
public final @NonNull Transition<@NonNull T> <S extends Object, T extends Object> TransitionKt.createChildTransition(
    @NonNull Transition<@NonNull S> receiver,
    @NonNull String label,
    @Composable @NonNull Function1<@NonNull parentState, @NonNull T> transformToChildState
)

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