androidx.compose.animation

In this page, you'll find documentation for types, properties, and functions available in the androidx.compose.animation package. For example:

If you're looking for guidance instead, check out the Animations in Compose guide.

Interfaces

AnimatedContentScope

Receiver scope for content lambda for AnimatedContent.

Cmn
AnimatedContentTransitionScope

AnimatedContentTransitionScope provides functions that are convenient and only applicable in the context of AnimatedContent, such as slideIntoContainer and slideOutOfContainer.

Cmn
AnimatedVisibilityScope

This is the scope for the content of AnimatedVisibility.

Cmn
BoundsTransform

BoundsTransform defines the animation spec used to animate from initial bounds to the target bounds.

Cmn
SharedTransitionScope

SharedTransitionScope provides a coordinator space in which shared elements/ shared bounds (when matched) will transform their bounds from one to another.

Cmn
SharedTransitionScope.OverlayClip

OverlayClip defines a specific clipping that should be applied to a sharedBounds or sharedElement in the overlay.

Cmn
SharedTransitionScope.PlaceHolderSize

PlaceHolderSize defines the size of the space that was or will be occupied by the exiting or entering sharedElement/sharedBounds.

Cmn
SharedTransitionScope.ResizeMode

There are two different modes to resize child layout of sharedBounds during bounds transform: 1) ScaleToBounds and 2) RemeasureToBounds.

Cmn
SizeTransform

SizeTransform defines how to transform from one size to another when the size of the content changes.

Cmn

Classes

AnimatedContentTransitionScope.SlideDirection

SlideDirection defines the direction of the slide in/out for slideIntoContainer and slideOutOfContainer.

Cmn
ContentTransform

ContentTransform defines how the target content (i.e. content associated with target state) enters AnimatedContent and how the initial content disappears.

Cmn
EnterTransition

EnterTransition defines how an AnimatedVisibility Composable appears on screen as it becomes visible.

Cmn
ExitTransition

ExitTransition defines how an AnimatedVisibility Composable disappears on screen as it becomes not visible.

Cmn
SharedTransitionScope.SharedContentState

SharedContentState is designed to allow access of the properties of sharedBounds/sharedElement, such as whether a match of the same key has been found in the SharedTransitionScope, its clipPathInOverlay and parentSharedContentState if there is a parent sharedBounds in the layout tree.

Cmn
SplineBasedFloatDecayAnimationSpec

A native Android fling curve decay.

Cmn

Annotations

Enums

EnterExitState

EnterExitState contains the three states that are involved in the enter and exit transition of AnimatedVisibility.

Cmn

Top-level functions summary

Animatable<ColorAnimationVector4D>
Animatable(initialValue: Color)

This Animatable function creates a Color value holder that automatically animates its value when the value is changed via animateTo.

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

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

Cmn
Unit
@Composable
AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier,
    enter: EnterTransition,
    exit: ExitTransition,
    label: String,
    content: @Composable AnimatedVisibilityScope.() -> Unit
)

AnimatedVisibility composable animates the appearance and disappearance of its content, as visible value changes.

Cmn
Unit
@Composable
AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,
    modifier: Modifier,
    enter: EnterTransition,
    exit: ExitTransition,
    label: String,
    content: @Composable AnimatedVisibilityScope.() -> Unit
)

AnimatedVisibility composable animates the appearance and disappearance of its content, as visibleState's targetState changes.

Cmn
Unit
@Composable
<T : Any?> Crossfade(
    targetState: T,
    modifier: Modifier,
    animationSpec: FiniteAnimationSpec<Float>,
    label: String,
    content: @Composable (T) -> Unit
)

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

Cmn
Unit

SharedTransitionLayout creates a layout and a SharedTransitionScope for the child layouts in content.

Cmn
Unit

SharedTransitionScope creates a SharedTransitionScope for the child layouts in content.

Cmn
SizeTransform
SizeTransform(
    clip: Boolean,
    sizeAnimationSpec: (initialSize: IntSize, targetSize: IntSize) -> FiniteAnimationSpec<IntSize>
)

This creates a SizeTransform with the provided clip and sizeAnimationSpec.

Cmn
State<Color>
@Composable
animateColorAsState(
    targetValue: Color,
    animationSpec: AnimationSpec<Color>,
    label: String,
    finishedListener: ((Color) -> Unit)?
)

Fire-and-forget animation function for Color.

Cmn
DecayAnimationSpec<Float>

This function is deprecated. Replace with rememberSplineBasedDecay<Float>

Cmn
android
EnterTransition
expandHorizontally(
    animationSpec: FiniteAnimationSpec<IntSize>,
    expandFrom: Alignment.Horizontal,
    clip: Boolean,
    initialWidth: (fullWidth: Int) -> Int
)

This expands the clip bounds of the appearing content horizontally, from the width returned from initialWidth to the full width.

Cmn
EnterTransition
expandIn(
    animationSpec: FiniteAnimationSpec<IntSize>,
    expandFrom: Alignment,
    clip: Boolean,
    initialSize: (fullSize: IntSize) -> IntSize
)

This expands the clip bounds of the appearing content from the size returned from initialSize to the full size.

Cmn
EnterTransition
expandVertically(
    animationSpec: FiniteAnimationSpec<IntSize>,
    expandFrom: Alignment.Vertical,
    clip: Boolean,
    initialHeight: (fullHeight: Int) -> Int
)

This expands the clip bounds of the appearing content vertically, from the height returned from initialHeight to the full height.

Cmn
EnterTransition
fadeIn(animationSpec: FiniteAnimationSpec<Float>, initialAlpha: Float)

This fades in the content of the transition, from the specified starting alpha (i.e. initialAlpha) to 1f, using the supplied animationSpec.

Cmn
ExitTransition
fadeOut(animationSpec: FiniteAnimationSpec<Float>, targetAlpha: Float)

This fades out the content of the transition, from full opacity to the specified target alpha (i.e. targetAlpha), using the supplied animationSpec.

Cmn
DecayAnimationSpec<T>
Cmn
android
EnterTransition
scaleIn(
    animationSpec: FiniteAnimationSpec<Float>,
    initialScale: Float,
    transformOrigin: TransformOrigin
)

This scales the content as it appears, from an initial scale (defined in initialScale) to 1f.

Cmn
ExitTransition
scaleOut(
    animationSpec: FiniteAnimationSpec<Float>,
    targetScale: Float,
    transformOrigin: TransformOrigin
)

This scales the content of the exit transition, from 1f to the target scale defined in targetScale.

Cmn
ExitTransition
shrinkHorizontally(
    animationSpec: FiniteAnimationSpec<IntSize>,
    shrinkTowards: Alignment.Horizontal,
    clip: Boolean,
    targetWidth: (fullWidth: Int) -> Int
)

This shrinks the clip bounds of the disappearing content horizontally, from the full width to the width returned from targetWidth.

Cmn
ExitTransition
shrinkOut(
    animationSpec: FiniteAnimationSpec<IntSize>,
    shrinkTowards: Alignment,
    clip: Boolean,
    targetSize: (fullSize: IntSize) -> IntSize
)

This shrinks the clip bounds of the disappearing content from the full size to the size returned from targetSize.

Cmn
ExitTransition
shrinkVertically(
    animationSpec: FiniteAnimationSpec<IntSize>,
    shrinkTowards: Alignment.Vertical,
    clip: Boolean,
    targetHeight: (fullHeight: Int) -> Int
)

This shrinks the clip bounds of the disappearing content vertically, from the full height to the height returned from targetHeight.

Cmn
EnterTransition
slideIn(
    animationSpec: FiniteAnimationSpec<IntOffset>,
    initialOffset: (fullSize: IntSize) -> IntOffset
)

This slides in the content of the transition, from a starting offset defined in initialOffset to IntOffset(0, 0).

Cmn
EnterTransition
slideInHorizontally(
    animationSpec: FiniteAnimationSpec<IntOffset>,
    initialOffsetX: (fullWidth: Int) -> Int
)

This slides in the content horizontally, from a starting offset defined in initialOffsetX to 0 pixels.

Cmn
EnterTransition
slideInVertically(
    animationSpec: FiniteAnimationSpec<IntOffset>,
    initialOffsetY: (fullHeight: Int) -> Int
)

This slides in the content vertically, from a starting offset defined in initialOffsetY to 0 in pixels.

Cmn
ExitTransition
slideOut(
    animationSpec: FiniteAnimationSpec<IntOffset>,
    targetOffset: (fullSize: IntSize) -> IntOffset
)

This slides out the content of the transition, from an offset of IntOffset(0, 0) to the target offset defined in targetOffset.

Cmn
ExitTransition
slideOutHorizontally(
    animationSpec: FiniteAnimationSpec<IntOffset>,
    targetOffsetX: (fullWidth: Int) -> Int
)

This slides out the content horizontally, from 0 to a target offset defined in targetOffsetX in pixels.

Cmn
ExitTransition
slideOutVertically(
    animationSpec: FiniteAnimationSpec<IntOffset>,
    targetOffsetY: (fullHeight: Int) -> Int
)

This slides out the content vertically, from 0 to a target offset defined in targetOffsetY in pixels.

Cmn
DecayAnimationSpec<T>
<T : Any?> splineBasedDecay(density: Density)
Cmn

Extension functions summary

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
@Composable
RowScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier,
    enter: EnterTransition,
    exit: ExitTransition,
    label: String,
    content: @Composable AnimatedVisibilityScope.() -> Unit
)

RowScope.AnimatedVisibility composable animates the appearance and disappearance of its content when the AnimatedVisibility is in a Row.

Cmn
Unit
@Composable
ColumnScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier,
    enter: EnterTransition,
    exit: ExitTransition,
    label: String,
    content: @Composable AnimatedVisibilityScope.() -> Unit
)

ColumnScope.AnimatedVisibility composable animates the appearance and disappearance of its content when the AnimatedVisibility is in a Column.

Cmn
Unit
@Composable
RowScope.AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,
    modifier: Modifier,
    enter: EnterTransition,
    exit: ExitTransition,
    label: String,
    content: @Composable AnimatedVisibilityScope.() -> Unit
)

RowScope.AnimatedVisibility composable animates the appearance and disappearance of its content as visibleState's targetState changes.

Cmn
Unit
@Composable
ColumnScope.AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,
    modifier: Modifier,
    enter: EnterTransition,
    exit: ExitTransition,
    label: String,
    content: @Composable AnimatedVisibilityScope.() -> Unit
)

ColumnScope.AnimatedVisibility composable animates the appearance and disappearance of its content as visibleState's targetState changes.

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
Modifier
@ExperimentalSharedTransitionApi
Modifier.animateBounds(
    lookaheadScope: LookaheadScope,
    modifier: Modifier,
    boundsTransform: BoundsTransform,
    animateMotionFrameOfReference: Boolean
)

Modifier to animate layout changes (position and/or size) that occur within a LookaheadScope.

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
State<Color>
@Composable
InfiniteTransition.animateColor(
    initialValue: Color,
    targetValue: Color,
    animationSpec: InfiniteRepeatableSpec<Color>,
    label: String
)

Creates a Color animation that runs infinitely as a part of the given InfiniteTransition.

Cmn
Modifier
Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec<IntSize>,
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)?
)

This modifier animates its own size when its child modifier (or the child composable if it is already at the tail of the chain) changes size.

Cmn
Modifier
Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec<IntSize>,
    alignment: Alignment,
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)?
)

This modifier animates its own size when its child modifier (or the child composable if it is already at the tail of the chain) changes size.

Cmn
infix ContentTransform

This creates a ContentTransform using the provided EnterTransition and exit, where the enter and exit transition will be running simultaneously.

Cmn
infix ContentTransform

This function is deprecated. Infix fun EnterTransition.with(ExitTransition) has been renamed to togetherWith

Cmn

Extension properties summary

(colorSpace: ColorSpace) -> TwoWayConverter<ColorAnimationVector4D>

A lambda that takes a ColorSpace and returns a converter that can both convert a Color to a AnimationVector4D, and convert a AnimationVector4D) back to a Color in the given ColorSpace.

Cmn

Top-level functions

fun Animatable(initialValue: Color): Animatable<ColorAnimationVector4D>

This Animatable function creates a Color value holder that automatically animates its value when the value is changed via animateTo. Animatable supports value change during an ongoing value change animation. When that happens, a new animation will transition Animatable from its current value (i.e. value at the point of interruption) to the new target. This ensures that the value change is always continuous using animateTo. If spring animation (i.e. default animation) is used with animateTo, the velocity change will be guaranteed to be continuous as well.

Unlike AnimationState, Animatable ensures mutual exclusiveness on its animation. To do so, when a new animation is started via animateTo (or animateDecay), any ongoing animation job will be cancelled via a CancellationException.

Animatable also supports animating data types other than Color, such as Floats and generic types. See androidx.compose.animation.core.Animatable for other variants.

import androidx.compose.animation.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.graphics.Color

@Composable
fun animate(
    targetValue: Color,
    animationSpec: AnimationSpec<Color>,
    onFinished: (Color) -> Unit
): Color {
    // Creates an Animatable of Color, and remembers it.
    val color = remember { Animatable(targetValue) }
    val finishedListener = rememberUpdatedState(onFinished)
    // Launches a new coroutine whenever the target value or animation spec has changed. This
    // automatically cancels the previous job/animation.
    LaunchedEffect(targetValue, animationSpec) {
        color.animateTo(targetValue, animationSpec)
        // Invokes finished listener. This line will not be executed if the job gets canceled
        // halfway through an animation.
        finishedListener.value(targetValue)
    }
    return color.value
}
Parameters
initialValue: Color

initial value of the Animatable

AnimatedContent

@Composable
fun <S : Any?> AnimatedContent(
    targetState: S,
    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,
    label: String = "AnimatedContent",
    contentKey: (targetState) -> Any? = { it },
    content: @Composable AnimatedContentScope.(targetState) -> Unit
): Unit

AnimatedContent is a container that automatically animates its content when 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 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 targetState is expected to mutate frequently and not all mutations should be treated as target state change, consider defining a mapping between 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 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 targetState when not animating. However, during the transient content transform, there will be more than one set of content present in the AnimatedContent container. It may be sometimes desired to define the positional relationship among the different content and the 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.

label is an optional parameter to differentiate from other animations in Android Studio.

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

// enum class ContentState { Foo, Bar, Baz }
@Composable
fun Foo() {
    Box(Modifier.size(200.dp).background(Color(0xffffdb00)))
}

@Composable
fun Bar() {
    Box(Modifier.size(40.dp).background(Color(0xffff8100)))
}

@Composable
fun Baz() {
    Box(Modifier.size(80.dp, 20.dp).background(Color(0xffff4400)))
}

var contentState: ContentState by remember { mutableStateOf(ContentState.Foo) }
AnimatedContent(contentState) {
    when (it) {
        // Specifies the mapping between a given ContentState and a composable function.
        ContentState.Foo -> Foo()
        ContentState.Bar -> Bar()
        ContentState.Baz -> Baz()
    }
}

Below is an example of customizing transitionSpec to imply a spatial relationship between the content for different states:

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
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.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

Column(Modifier.padding(20.dp), horizontalAlignment = Alignment.CenterHorizontally) {
    var count by remember { mutableStateOf(0) }
    // The `AnimatedContent` below uses an integer count as its target state. So when the
    // count changes, it will animate out the content associated with the previous count, and
    // animate in the content associated with the target state.
    AnimatedContent(
        targetState = count,
        transitionSpec = {
            // We can define how the new target content comes in and how initial content
            // leaves in the ContentTransform. Here we want to create the impression that the
            // different numbers have a spatial relationship - larger numbers are
            // positioned (vertically) below smaller numbers.
            if (targetState > initialState) {
                    // If the incoming number is larger, new number slides up and fades in while
                    // the previous (smaller) number slides up to make room and fades out.
                    slideInVertically { it } + fadeIn() togetherWith
                        slideOutVertically { -it } + fadeOut()
                } else {
                    // If the incoming number is smaller, new number slides down and fades in
                    // while
                    // the previous number slides down and fades out.
                    slideInVertically { -it } + fadeIn() togetherWith
                        slideOutVertically { it } + fadeOut()
                    // Disable clipping since the faded slide-out is desired out of bounds, but
                    // the size transform is still needed from number getting longer
                }
                .using(SizeTransform(clip = false)) // Using default spring for the size change.
        }
    ) { targetCount ->
        // This establishes a mapping between the target state and the content in the form of a
        // Composable function. IMPORTANT: The parameter of this content lambda should
        // *always* be used. During the content transform, the old content will be looked up
        // using this lambda with the old state, until it's fully animated out.

        // Since AnimatedContent differentiates the contents using their target states as the
        // key, the same composable function returned by the content lambda like below will be
        // invoked under different keys and therefore treated as different entities.
        Text("$targetCount", fontSize = 20.sp)
    }
    Spacer(Modifier.size(20.dp))
    Row(horizontalArrangement = Arrangement.SpaceAround) {
        Button(onClick = { count-- }) { Text("Minus") }
        Spacer(Modifier.size(60.dp))
        Button(onClick = { count++ }) { Text("Plus ") }
    }
}

AnimatedVisibility

@Composable
fun AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
): Unit

AnimatedVisibility composable animates the appearance and disappearance of its content, as visible value changes. 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 the second sample code snippet below for example. These custom animations will be running alongside 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. If there's a need to observe the state change of the enter/exit transition and follow up additional action (e.g. remove data, sequential animation, etc), consider the AnimatedVisibility API variant that takes a MutableTransitionState parameter.

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. When the AnimatedVisibility composable is put in a Row or a Column, the default enter and exit transitions are tailored to that particular container. See RowScope.AnimatedVisibility and ColumnScope.AnimatedVisibility for details.

Here are two examples of AnimatedVisibility: one using the built-in enter/exit transition, the other using a custom enter/exit animation.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOut
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
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.TransformOrigin
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible,
    enter =
        slideInVertically(
            // Start the slide from 40 (pixels) above where the content is supposed to go, to
            // produce a parallax effect
            initialOffsetY = { -40 }
        ) +
            expandVertically(expandFrom = Alignment.Top) +
            scaleIn(
                // Animate scale from 0f to 1f using the top center as the pivot point.
                transformOrigin = TransformOrigin(0.5f, 0f)
            ) +
            fadeIn(initialAlpha = 0.3f),
    exit = slideOutVertically() + shrinkVertically() + fadeOut() + scaleOut(targetScale = 1.2f)
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}

The example blow shows how a custom enter/exit animation can be created using the Transition object (i.e. Transition) from AnimatedVisibilityScope.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.EnterExitState
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
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

var visible by remember { mutableStateOf(true) }
Box(modifier = Modifier.clickable { visible = !visible }) {
    AnimatedVisibility(
        visible = visible,
        modifier = Modifier.align(Alignment.Center),
        enter = fadeIn(),
        exit = fadeOut(animationSpec = tween(200)) + scaleOut()
    ) { // Content that needs to appear/disappear goes here:
        // Here we can optionally define a custom enter/exit animation by creating an animation
        // using the Transition<EnterExitState> object from AnimatedVisibilityScope:

        // As a part of the enter transition, the corner radius will be animated from 0.dp to
        // 50.dp.
        val cornerRadius by
            transition.animateDp {
                when (it) {
                    EnterExitState.PreEnter -> 0.dp
                    EnterExitState.Visible -> 50.dp
                    // No corner radius change when exiting.
                    EnterExitState.PostExit -> 50.dp
                }
            }
        Box(
            Modifier.background(Color.Red, shape = RoundedCornerShape(cornerRadius))
                .height(100.dp)
                .fillMaxWidth()
        )
    }
}
Parameters
visible: Boolean

defines whether the content should be visible

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 by default

exit: ExitTransition = shrinkOut() + fadeOut()

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

content: @Composable AnimatedVisibilityScope.() -> Unit

Content to appear or disappear based on the value of visible

AnimatedVisibility

@Composable
fun AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = fadeOut() + shrinkOut(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
): Unit

AnimatedVisibility composable animates the appearance and disappearance of its content, as visibleState's targetState changes. The visibleState can also be used to observe the state of AnimatedVisibility. For example: visibleState.isIdle indicates whether all the animations have finished in AnimatedVisibility, and visibleState.currentState returns the initial state of the current animations.

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. Both currentState and targetState will be false for visibleState.

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. When the AnimatedVisibility composable is put in a Row or a Column, the default enter and exit transitions are tailored to that particular container. See RowScope.AnimatedVisibility and ColumnScope.AnimatedVisibility for details.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
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.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

val turquoiseColors =
    listOf(
        Color(0xff07688C),
        Color(0xff1986AF),
        Color(0xff50B6CD),
        Color(0xffBCF8FF),
        Color(0xff8AEAE9),
        Color(0xff46CECA)
    )

// MyModel class handles the data change of the items that are displayed in LazyColumn.
class MyModel {
    private val _items: MutableList<ColoredItem> = mutableStateListOf()
    private var lastItemId = 0
    val items: List<ColoredItem> = _items

    // Each item has a MutableTransitionState field to track as well as to mutate the item's
    // visibility. When the MutableTransitionState's targetState changes, corresponding
    // transition will be fired. MutableTransitionState allows animation lifecycle to be
    // observed through it's [currentState] and [isIdle]. See below for details.
    inner class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
        val color: Color
            get() = turquoiseColors.let { it[itemId % it.size] }
    }

    fun addNewItem() {
        lastItemId++
        _items.add(
            ColoredItem(
                // Here the initial state of the MutableTransitionState is set to false, and
                // target state is set to true. This will result in an enter transition for
                // the newly added item.
                MutableTransitionState(false).apply { targetState = true },
                lastItemId
            )
        )
    }

    fun removeItem(item: ColoredItem) {
        // By setting the targetState to false, this will effectively trigger an exit
        // animation in AnimatedVisibility.
        item.visible.targetState = false
    }

    fun pruneItems() {
        // Inspect the animation status through MutableTransitionState. If isIdle == true,
        // all animations have finished for the transition.
        _items.removeAll(
            items.filter {
                // This checks that the animations have finished && the animations are exit
                // transitions. In other words, the item has finished animating out.
                it.visible.isIdle && !it.visible.targetState
            }
        )
    }

    fun removeAll() {
        _items.forEach { it.visible.targetState = false }
    }
}

@Composable
fun AnimatedVisibilityInLazyColumn() {
    Column {
        val model = remember { MyModel() }
        Row(Modifier.fillMaxWidth()) {
            Button({ model.addNewItem() }, modifier = Modifier.padding(15.dp).weight(1f)) {
                Text("Add")
            }
        }

        // This sets up a flow to check whether any item has finished animating out. If yes,
        // notify the model to prune the list.
        LaunchedEffect(model) {
            snapshotFlow {
                    model.items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
                }
                .collect {
                    if (it != null) {
                        model.pruneItems()
                    }
                }
        }
        LazyColumn {
            items(model.items, key = { it.itemId }) { item ->
                AnimatedVisibility(
                    item.visible,
                    enter = expandVertically(),
                    exit = shrinkVertically()
                ) {
                    Box(Modifier.fillMaxWidth().requiredHeight(90.dp).background(item.color)) {
                        Button(
                            { model.removeItem(item) },
                            modifier = Modifier.align(Alignment.CenterEnd).padding(15.dp)
                        ) {
                            Text("Remove")
                        }
                    }
                }
            }
        }

        Button({ model.removeAll() }, modifier = Modifier.align(Alignment.End).padding(15.dp)) {
            Text("Clear All")
        }
    }
}
Parameters
visibleState: MutableTransitionState<Boolean>

defines whether the content should be visible

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 by default

exit: ExitTransition = fadeOut() + shrinkOut()

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

content: @Composable AnimatedVisibilityScope.() -> Unit

Content to appear or disappear based on the value of visibleState

Crossfade

@Composable
fun <T : Any?> Crossfade(
    targetState: T,
    modifier: Modifier = Modifier,
    animationSpec: FiniteAnimationSpec<Float> = tween(),
    label: String = "Crossfade",
    content: @Composable (T) -> Unit
): Unit

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

import androidx.compose.animation.Crossfade
import androidx.compose.material.Text

Crossfade(targetState = "A") { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}
Parameters
targetState: T

is a key representing your target layout state. Every time you change a key the animation will be triggered. The content called with the old key will be faded out while the content called with the new key will be faded in.

modifier: Modifier = Modifier

Modifier to be applied to the animation container.

animationSpec: FiniteAnimationSpec<Float> = tween()

the AnimationSpec to configure the animation.

label: String = "Crossfade"

An optional label to differentiate from other animations in Android Studio.

SharedTransitionLayout

@ExperimentalSharedTransitionApi
@Composable
fun SharedTransitionLayout(
    modifier: Modifier = Modifier,
    content: @Composable SharedTransitionScope.() -> Unit
): Unit

SharedTransitionLayout creates a layout and a SharedTransitionScope for the child layouts in content. Any child (direct or indirect) of the SharedTransitionLayout can use the receiver scope SharedTransitionScope to create shared element or shared bounds transitions.

Note: SharedTransitionLayout creates a new Layout. For use cases where it's preferable to not introduce a new layout between content and the parent layout, consider using SharedTransitionScope instead.

Parameters
modifier: Modifier = Modifier

Modifiers to be applied to the layout.

content: @Composable SharedTransitionScope.() -> Unit

The children composable to be laid out.

SharedTransitionScope

@ExperimentalSharedTransitionApi
@Composable
fun SharedTransitionScope(content: @Composable SharedTransitionScope.(Modifier) -> Unit): Unit

SharedTransitionScope creates a SharedTransitionScope for the child layouts in content. Any child (direct or indirect) of the SharedTransitionLayout can use the receiver scope SharedTransitionScope to create shared element or shared bounds transitions. SharedTransitionScope will not creates a new Layout.

IMPORTANT: It is important to set the Modifier provided to the content on the first and top-most child, as the Modifier both obtains the root coordinates and creates an overlay. If the first child layout in content isn't the child with the highest zIndex, consider using SharedTransitionLayout instead.

Parameters
content: @Composable SharedTransitionScope.(Modifier) -> Unit

The children composable to be laid out.

SizeTransform

fun SizeTransform(
    clip: Boolean = true,
    sizeAnimationSpec: (initialSize: IntSize, targetSize: IntSize) -> FiniteAnimationSpec<IntSize> = { _, _ -> spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ) }
): SizeTransform

This creates a SizeTransform with the provided clip and sizeAnimationSpec. By default, clip will be true. This means during the size animation, the content will be clipped to the animated size. sizeAnimationSpec defaults to return a spring animation.

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.ui.unit.IntSize

// enum class CartState { Expanded, Collapsed }
val transitionSpec: AnimatedContentTransitionScope<CartState>.() -> ContentTransform = {
    // Fade in with a delay so that it starts after fade out
    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
                    }
                }
            }
        )
}

animateColorAsState

@Composable
fun animateColorAsState(
    targetValue: Color,
    animationSpec: AnimationSpec<Color> = colorDefaultSpring,
    label: String = "ColorAnimation",
    finishedListener: ((Color) -> Unit)? = null
): State<Color>

Fire-and-forget animation function for Color. This Composable function is overloaded for different parameter types such as Dp, Float, Int, Size, Offset, etc. When the provided targetValue is changed, the animation will run automatically. If there is already an animation in-flight when targetValue changes, the on-going animation will adjust course to animate towards the new target value.

animateColorAsState returns a State object. The value of the state object will continuously be updated by the animation until the animation finishes.

Note, animateColorAsState cannot be canceled/stopped without removing this composable function from the tree. See Animatable for cancelable animations.

import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color

@Composable
fun ColorAnimation(primary: Boolean) {
    // Animates to primary or secondary color, depending on whether [primary] is true
    // [animateState] returns the current animation value in a State<Color> in this example. We
    // use the State<Color> object as a property delegate here.
    val color: Color by
        animateColorAsState(
            if (primary) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
        )
    Box(modifier = Modifier.background(color))
}
Parameters
targetValue: Color

Target value of the animation

animationSpec: AnimationSpec<Color> = colorDefaultSpring

The animation that will be used to change the value through time, spring by default

label: String = "ColorAnimation"

An optional label to differentiate from other animations in Android Studio.

finishedListener: ((Color) -> Unit)? = null

An optional listener to get notified when the animation is finished.

defaultDecayAnimationSpec

@Composable
fun defaultDecayAnimationSpec(): DecayAnimationSpec<Float>

Create default DecayAnimationSpec representing a default fling curve for a platform.

expandHorizontally

fun expandHorizontally(
    animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ),
    expandFrom: Alignment.Horizontal = Alignment.End,
    clip: Boolean = true,
    initialWidth: (fullWidth: Int) -> Int = { 0 }
): EnterTransition

This expands the clip bounds of the appearing content horizontally, from the width returned from initialWidth to the full width. expandFrom controls which part of the content gets revealed first. By default, the clip bounds animates from 0 to full width, starting from the end of the content, and expand to fully revealing the whole content.

Note: expandHorizontally animates the bounds of the content. This bounds change will also result in the animation of other layouts that are dependent on this size.

initialWidth is a lambda that takes the full width of the content and returns an initial width of the bounds of the content. This allows not only an absolute width, but also an initial width that is proportional to the content width.

clip defines whether the content outside of the animated bounds should be clipped. By default, clip is set to true, which only shows content in the animated bounds.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible,
    // Set the start width to 20 (pixels), 0 by default
    enter = expandHorizontally { 20 },
    exit =
        shrinkHorizontally(
            // Overwrites the default animation with tween for this shrink animation.
            animationSpec = tween(),
            // Shrink towards the end (i.e. right edge for LTR, left edge for RTL). The default
            // direction for the shrink is towards [Alignment.Start]
            shrinkTowards = Alignment.End,
        ) { fullWidth ->
            // Set the end width for the shrink animation to a quarter of the full width.
            fullWidth / 4
        }
) {
    // Content that needs to appear/disappear goes here:
    Box(Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold )

the animation used for the expanding animation, spring by default.

expandFrom: Alignment.Horizontal = Alignment.End

the starting point of the expanding bounds, Alignment.End by default.

clip: Boolean = true

whether the content outside of the animated bounds should be clipped, true by default

initialWidth: (fullWidth: Int) -> Int = { 0 }

the start width of the expanding bounds, returning 0 by default.

expandIn

fun expandIn(
    animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ),
    expandFrom: Alignment = Alignment.BottomEnd,
    clip: Boolean = true,
    initialSize: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) }
): EnterTransition

This expands the clip bounds of the appearing content from the size returned from initialSize to the full size. expandFrom controls which part of the content gets revealed first. By default, the clip bounds animates from IntSize(0, 0) to full size, starting from revealing the bottom right corner (or bottom left corner in RTL layouts) of the content, to fully revealing the entire content as the size expands.

Note: expandIn animates the bounds of the content. This bounds change will also result in the animation of other layouts that are dependent on this size.

initialSize is a lambda that takes the full size of the content and returns an initial size of the bounds of the content. This allows not only absolute size, but also an initial size that is proportional to the content size.

clip defines whether the content outside of the animated bounds should be clipped. By default, clip is set to true, which only shows content in the animated bounds.

For expanding only horizontally or vertically, consider expandHorizontally, expandVertically.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
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.clip
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible,
    enter =
        expandIn(
            // Overwrites the default spring animation with tween
            animationSpec = tween(100, easing = LinearOutSlowInEasing),
            // Overwrites the corner of the content that is first revealed
            expandFrom = Alignment.BottomStart
        ) {
            // Overwrites the initial size to 50 pixels by 50 pixels
            IntSize(50, 50)
        },
    exit =
        shrinkOut(
            tween(100, easing = FastOutSlowInEasing),
            // Overwrites the area of the content that the shrink animation will end on. The
            // following parameters will shrink the content's clip bounds from the full size of
            // the
            // content to 1/10 of the width and 1/5 of the height. The shrinking clip bounds
            // will
            // always be aligned to the CenterStart of the full-content bounds.
            shrinkTowards = Alignment.CenterStart
        ) { fullSize ->
            // Overwrites the target size of the shrinking animation.
            IntSize(fullSize.width / 10, fullSize.height / 5)
        }
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold )

the animation used for the expanding animation, spring by default.

expandFrom: Alignment = Alignment.BottomEnd

the starting point of the expanding bounds, Alignment.BottomEnd by default.

clip: Boolean = true

whether the content outside of the animated bounds should be clipped, true by default

initialSize: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) }

the start size of the expanding bounds, returning IntSize(0, 0) by default.

expandVertically

fun expandVertically(
    animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ),
    expandFrom: Alignment.Vertical = Alignment.Bottom,
    clip: Boolean = true,
    initialHeight: (fullHeight: Int) -> Int = { 0 }
): EnterTransition

This expands the clip bounds of the appearing content vertically, from the height returned from initialHeight to the full height. expandFrom controls which part of the content gets revealed first. By default, the clip bounds animates from 0 to full height, revealing the bottom edge first, followed by the rest of the content.

Note: expandVertically animates the bounds of the content. This bounds change will also result in the animation of other layouts that are dependent on this size.

initialHeight is a lambda that takes the full height of the content and returns an initial height of the bounds of the content. This allows not only an absolute height, but also an initial height that is proportional to the content height.

clip defines whether the content outside of the animated bounds should be clipped. By default, clip is set to true, which only shows content in the animated bounds.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.requiredHeight
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.unit.dp

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible,
    // Sets the initial height of the content to 20, revealing only the top of the content at
    // the beginning of the expanding animation.
    enter = expandVertically(expandFrom = Alignment.Top) { 20 },
    // Shrinks the content to half of its full height via an animation.
    exit = shrinkVertically(animationSpec = tween()) { fullHeight -> fullHeight / 2 },
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold )

the animation used for the expanding animation, spring by default.

expandFrom: Alignment.Vertical = Alignment.Bottom

the starting point of the expanding bounds, Alignment.Bottom by default.

clip: Boolean = true

whether the content outside of the animated bounds should be clipped, true by default

initialHeight: (fullHeight: Int) -> Int = { 0 }

the start height of the expanding bounds, returning 0 by default.

fun fadeIn(
    animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
    initialAlpha: Float = 0.0f
): EnterTransition

This fades in the content of the transition, from the specified starting alpha (i.e. initialAlpha) to 1f, using the supplied animationSpec. initialAlpha defaults to 0f, and spring is used by default.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible,
    enter =
        fadeIn(
            // Overwrites the initial value of alpha to 0.4f for fade in, 0 by default
            initialAlpha = 0.4f
        ),
    exit =
        fadeOut(
            // Overwrites the default animation with tween
            animationSpec = tween(durationMillis = 250)
        )
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow)

the FiniteAnimationSpec for this animation, spring by default

initialAlpha: Float = 0.0f

the starting alpha of the enter transition, 0f by default

fadeOut

fun fadeOut(
    animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
    targetAlpha: Float = 0.0f
): ExitTransition

This fades out the content of the transition, from full opacity to the specified target alpha (i.e. targetAlpha), using the supplied animationSpec. By default, the content will be faded out to fully transparent (i.e. targetAlpha defaults to 0), and animationSpec uses spring by default.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible,
    enter =
        fadeIn(
            // Overwrites the initial value of alpha to 0.4f for fade in, 0 by default
            initialAlpha = 0.4f
        ),
    exit =
        fadeOut(
            // Overwrites the default animation with tween
            animationSpec = tween(durationMillis = 250)
        )
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow)

the FiniteAnimationSpec for this animation, spring by default

targetAlpha: Float = 0.0f

the target alpha of the exit transition, 0f by default

rememberSplineBasedDecay

@Composable
fun <T : Any?> rememberSplineBasedDecay(): DecayAnimationSpec<T>
fun scaleIn(
    animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
    initialScale: Float = 0.0f,
    transformOrigin: TransformOrigin = TransformOrigin.Center
): EnterTransition

This scales the content as it appears, from an initial scale (defined in initialScale) to 1f. transformOrigin defines the pivot point in terms of fraction of the overall size. TransformOrigin.Center by default. scaleIn can be used in combination with any other type of EnterTransition using the plus operator (e.g. scaleIn() + slideInHorizontally())

Note: Scale is applied before slide. This means when using slideIn/slideOut with scaleIn/scaleOut, the amount of scaling needs to be taken into account when sliding.

The scaling will change the visual of the content, but will not affect the layout size. scaleIn can be combined with expandIn/expandHorizontally/expandVertically to coordinate layout size change while scaling. For example:

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.expandIn
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
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.graphics.TransformOrigin
import androidx.compose.ui.unit.dp

Column {
    var showRed by remember { mutableStateOf(true) }
    var showGreen by remember { mutableStateOf(true) }

    AnimatedVisibility(
        visible = showGreen,
        // By Default, `scaleIn` uses the center as its pivot point. When used with a vertical
        // expansion from the vertical center, the content will be growing from the center of
        // the vertically expanding layout.
        enter = scaleIn() + expandVertically(expandFrom = Alignment.CenterVertically),
        // By Default, `scaleOut` uses the center as its pivot point. When used with an
        // ExitTransition that shrinks towards the center, the content will be shrinking both
        // in terms of scale and layout size towards the center.
        exit = scaleOut() + shrinkVertically(shrinkTowards = Alignment.CenterVertically)
    ) {
        Box(
            Modifier.size(100.dp)
                .background(color = Color.Green, shape = RoundedCornerShape(20.dp))
        )
    }

    AnimatedVisibility(
        visible = showRed,
        // Scale up from the TopLeft by setting TransformOrigin to (0f, 0f), while expanding the
        // layout size from Top start and fading. This will create a coherent look as if the
        // scale is impacting the size.
        enter =
            scaleIn(transformOrigin = TransformOrigin(0f, 0f)) +
                fadeIn() +
                expandIn(expandFrom = Alignment.TopStart),
        // Scale down from the TopLeft by setting TransformOrigin to (0f, 0f), while shrinking
        // the layout towards Top start and fading. This will create a coherent look as if the
        // scale is impacting the layout size.
        exit =
            scaleOut(transformOrigin = TransformOrigin(0f, 0f)) +
                fadeOut() +
                shrinkOut(shrinkTowards = Alignment.TopStart)
    ) {
        Box(
            Modifier.size(100.dp)
                .background(color = Color.Red, shape = RoundedCornerShape(20.dp))
        )
    }
}
Parameters
animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow)

the animation used for the scale-out, spring by default.

initialScale: Float = 0.0f

the initial scale for the enter transition, 0 by default.

transformOrigin: TransformOrigin = TransformOrigin.Center

the pivot point in terms of fraction of the overall size. By default it's TransformOrigin.Center.

fun scaleOut(
    animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
    targetScale: Float = 0.0f,
    transformOrigin: TransformOrigin = TransformOrigin.Center
): ExitTransition

This scales the content of the exit transition, from 1f to the target scale defined in targetScale. transformOrigin defines the pivot point in terms of fraction of the overall size. By default it's TransformOrigin.Center. scaleOut can be used in combination with any other type of ExitTransition using the plus operator (e.g. scaleOut() + fadeOut())

Note: Scale is applied before slide. This means when using slideIn/slideOut with scaleIn/scaleOut, the amount of scaling needs to be taken into account when sliding.

The scaling will change the visual of the content, but will not affect the layout size. scaleOut can be combined with shrinkOut/shrinkHorizontally/shrinkVertically for coordinated layout size change animation. For example:

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.expandIn
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
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.graphics.TransformOrigin
import androidx.compose.ui.unit.dp

Column {
    var showRed by remember { mutableStateOf(true) }
    var showGreen by remember { mutableStateOf(true) }

    AnimatedVisibility(
        visible = showGreen,
        // By Default, `scaleIn` uses the center as its pivot point. When used with a vertical
        // expansion from the vertical center, the content will be growing from the center of
        // the vertically expanding layout.
        enter = scaleIn() + expandVertically(expandFrom = Alignment.CenterVertically),
        // By Default, `scaleOut` uses the center as its pivot point. When used with an
        // ExitTransition that shrinks towards the center, the content will be shrinking both
        // in terms of scale and layout size towards the center.
        exit = scaleOut() + shrinkVertically(shrinkTowards = Alignment.CenterVertically)
    ) {
        Box(
            Modifier.size(100.dp)
                .background(color = Color.Green, shape = RoundedCornerShape(20.dp))
        )
    }

    AnimatedVisibility(
        visible = showRed,
        // Scale up from the TopLeft by setting TransformOrigin to (0f, 0f), while expanding the
        // layout size from Top start and fading. This will create a coherent look as if the
        // scale is impacting the size.
        enter =
            scaleIn(transformOrigin = TransformOrigin(0f, 0f)) +
                fadeIn() +
                expandIn(expandFrom = Alignment.TopStart),
        // Scale down from the TopLeft by setting TransformOrigin to (0f, 0f), while shrinking
        // the layout towards Top start and fading. This will create a coherent look as if the
        // scale is impacting the layout size.
        exit =
            scaleOut(transformOrigin = TransformOrigin(0f, 0f)) +
                fadeOut() +
                shrinkOut(shrinkTowards = Alignment.TopStart)
    ) {
        Box(
            Modifier.size(100.dp)
                .background(color = Color.Red, shape = RoundedCornerShape(20.dp))
        )
    }
}
Parameters
animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow)

the animation used for the slide-out, spring by default.

targetScale: Float = 0.0f

the target scale for the exit transition, 0 by default.

transformOrigin: TransformOrigin = TransformOrigin.Center

the pivot point in terms of fraction of the overall size. By default it's TransformOrigin.Center.

shrinkHorizontally

fun shrinkHorizontally(
    animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ),
    shrinkTowards: Alignment.Horizontal = Alignment.End,
    clip: Boolean = true,
    targetWidth: (fullWidth: Int) -> Int = { 0 }
): ExitTransition

This shrinks the clip bounds of the disappearing content horizontally, from the full width to the width returned from targetWidth. shrinkTowards controls the direction of the bounds shrink animation. By default, the clip bounds animates from full width to 0, shrinking towards the end of the content.

Note: shrinkHorizontally animates the bounds of the content. This bounds change will also result in the animation of other layouts that are dependent on this size.

targetWidth is a lambda that takes the full width of the content and returns a target width of the content. This allows not only absolute width, but also a target width that is proportional to the content width.

clip defines whether the content outside of the animated bounds should be clipped. By default, clip is set to true, which only shows content in the animated bounds.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible,
    // Set the start width to 20 (pixels), 0 by default
    enter = expandHorizontally { 20 },
    exit =
        shrinkHorizontally(
            // Overwrites the default animation with tween for this shrink animation.
            animationSpec = tween(),
            // Shrink towards the end (i.e. right edge for LTR, left edge for RTL). The default
            // direction for the shrink is towards [Alignment.Start]
            shrinkTowards = Alignment.End,
        ) { fullWidth ->
            // Set the end width for the shrink animation to a quarter of the full width.
            fullWidth / 4
        }
) {
    // Content that needs to appear/disappear goes here:
    Box(Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold )

the animation used for the shrinking animation, spring by default.

shrinkTowards: Alignment.Horizontal = Alignment.End

the ending point of the shrinking bounds, Alignment.End by default.

clip: Boolean = true

whether the content outside of the animated bounds should be clipped, true by default

targetWidth: (fullWidth: Int) -> Int = { 0 }

returns the end width of the shrinking bounds, 0 by default.

shrinkOut

fun shrinkOut(
    animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ),
    shrinkTowards: Alignment = Alignment.BottomEnd,
    clip: Boolean = true,
    targetSize: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) }
): ExitTransition

This shrinks the clip bounds of the disappearing content from the full size to the size returned from targetSize. shrinkTowards controls the direction of the bounds shrink animation. By default, the clip bounds animates from full size to IntSize(0, 0), shrinking towards the the bottom right corner (or bottom left corner in RTL layouts) of the content.

Note: shrinkOut animates the bounds of the content. This bounds change will also result in the animation of other layouts that are dependent on this size.

targetSize is a lambda that takes the full size of the content and returns a target size of the bounds of the content. This allows not only absolute size, but also a target size that is proportional to the content size.

clip defines whether the content outside of the animated bounds should be clipped. By default, clip is set to true, which only shows content in the animated bounds.

For shrinking only horizontally or vertically, consider shrinkHorizontally, shrinkVertically.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
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.clip
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible,
    enter =
        expandIn(
            // Overwrites the default spring animation with tween
            animationSpec = tween(100, easing = LinearOutSlowInEasing),
            // Overwrites the corner of the content that is first revealed
            expandFrom = Alignment.BottomStart
        ) {
            // Overwrites the initial size to 50 pixels by 50 pixels
            IntSize(50, 50)
        },
    exit =
        shrinkOut(
            tween(100, easing = FastOutSlowInEasing),
            // Overwrites the area of the content that the shrink animation will end on. The
            // following parameters will shrink the content's clip bounds from the full size of
            // the
            // content to 1/10 of the width and 1/5 of the height. The shrinking clip bounds
            // will
            // always be aligned to the CenterStart of the full-content bounds.
            shrinkTowards = Alignment.CenterStart
        ) { fullSize ->
            // Overwrites the target size of the shrinking animation.
            IntSize(fullSize.width / 10, fullSize.height / 5)
        }
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold )

the animation used for the shrinking animation, spring by default.

shrinkTowards: Alignment = Alignment.BottomEnd

the ending point of the shrinking bounds, Alignment.BottomEnd by default.

clip: Boolean = true

whether the content outside of the animated bounds should be clipped, true by default

targetSize: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) }

returns the end size of the shrinking bounds, IntSize(0, 0) by default.

shrinkVertically

fun shrinkVertically(
    animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ),
    shrinkTowards: Alignment.Vertical = Alignment.Bottom,
    clip: Boolean = true,
    targetHeight: (fullHeight: Int) -> Int = { 0 }
): ExitTransition

This shrinks the clip bounds of the disappearing content vertically, from the full height to the height returned from targetHeight. shrinkTowards controls the direction of the bounds shrink animation. By default, the clip bounds animates from full height to 0, shrinking towards the bottom of the content.

Note: shrinkVertically animates the bounds of the content. This bounds change will also result in the animation of other layouts that are dependent on this size.

targetHeight is a lambda that takes the full height of the content and returns a target height of the content. This allows not only absolute height, but also a target height that is proportional to the content height.

clip defines whether the content outside of the animated bounds should be clipped. By default, clip is set to true, which only shows content in the animated bounds.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.requiredHeight
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.unit.dp

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible,
    // Sets the initial height of the content to 20, revealing only the top of the content at
    // the beginning of the expanding animation.
    enter = expandVertically(expandFrom = Alignment.Top) { 20 },
    // Shrinks the content to half of its full height via an animation.
    exit = shrinkVertically(animationSpec = tween()) { fullHeight -> fullHeight / 2 },
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold )

the animation used for the shrinking animation, spring by default.

shrinkTowards: Alignment.Vertical = Alignment.Bottom

the ending point of the shrinking bounds, Alignment.Bottom by default.

clip: Boolean = true

whether the content outside of the animated bounds should be clipped, true by default

targetHeight: (fullHeight: Int) -> Int = { 0 }

returns the end height of the shrinking bounds, 0 by default.

slideIn

fun slideIn(
    animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold ),
    initialOffset: (fullSize: IntSize) -> IntOffset
): EnterTransition

This slides in the content of the transition, from a starting offset defined in initialOffset to IntOffset(0, 0). The direction of the slide can be controlled by configuring the initialOffset. A positive x value means sliding from right to left, whereas a negative x value will slide the content to the right. Similarly positive and negative y values correspond to sliding up and down, respectively.

If the sliding is only desired horizontally or vertically, instead of along both axis, consider using slideInHorizontally or slideInVertically.

initialOffset is a lambda that takes the full size of the content and returns an offset. This allows the offset to be defined proportional to the full size, or as an absolute value.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible,
    enter =
        slideIn(tween(100, easing = LinearOutSlowInEasing)) { fullSize ->
            // Specifies the starting offset of the slide-in to be 1/4 of the width to the
            // right,
            // 100 (pixels) below the content position, which results in a simultaneous slide up
            // and slide left.
            IntOffset(fullSize.width / 4, 100)
        },
    exit =
        slideOut(tween(100, easing = FastOutSlowInEasing)) {
            // The offset can be entirely independent of the size of the content. This specifies
            // a target offset 180 pixels to the left of the content, and 50 pixels below. This
            // will
            // produce a slide-left combined with a slide-down.
            IntOffset(-180, 50)
        },
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold )

the animation used for the slide-in, spring by default.

initialOffset: (fullSize: IntSize) -> IntOffset

a lambda that takes the full size of the content and returns the initial offset for the slide-in

slideInHorizontally

fun slideInHorizontally(
    animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold ),
    initialOffsetX: (fullWidth: Int) -> Int = { -it / 2 }
): EnterTransition

This slides in the content horizontally, from a starting offset defined in initialOffsetX to 0 pixels. The direction of the slide can be controlled by configuring the initialOffsetX. A positive value means sliding from right to left, whereas a negative value would slide the content from left to right.

initialOffsetX is a lambda that takes the full width of the content and returns an offset. This allows the starting offset to be defined proportional to the full size, or as an absolute value. It defaults to return half of negative width, which would offset the content to the left by half of its width, and slide towards the right.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOut
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible,
    enter =
        slideInHorizontally(animationSpec = tween(durationMillis = 200)) { fullWidth ->
            // Offsets the content by 1/3 of its width to the left, and slide towards right
            // Overwrites the default animation with tween for this slide animation.
            -fullWidth / 3
        } +
            fadeIn(
                // Overwrites the default animation with tween
                animationSpec = tween(durationMillis = 200)
            ),
    exit =
        slideOutHorizontally(animationSpec = spring(stiffness = Spring.StiffnessHigh)) {
            // Overwrites the ending position of the slide-out to 200 (pixels) to the right
            200
        } + fadeOut()
) {
    // Content that needs to appear/disappear goes here:
    Box(Modifier.fillMaxWidth().requiredHeight(200.dp)) {}
}
Parameters
animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold )

the animation used for the slide-in, spring by default.

initialOffsetX: (fullWidth: Int) -> Int = { -it / 2 }

a lambda that takes the full width of the content in pixels and returns the initial offset for the slide-in, by default it returns -fullWidth/2

slideInVertically

fun slideInVertically(
    animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold ),
    initialOffsetY: (fullHeight: Int) -> Int = { -it / 2 }
): EnterTransition

This slides in the content vertically, from a starting offset defined in initialOffsetY to 0 in pixels. The direction of the slide can be controlled by configuring the initialOffsetY. A positive initial offset means sliding up, whereas a negative value would slide the content down.

initialOffsetY is a lambda that takes the full Height of the content and returns an offset. This allows the starting offset to be defined proportional to the full height, or as an absolute value. It defaults to return half of negative height, which would offset the content up by half of its Height, and slide down.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOut
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
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.TransformOrigin
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible,
    enter =
        slideInVertically(
            // Start the slide from 40 (pixels) above where the content is supposed to go, to
            // produce a parallax effect
            initialOffsetY = { -40 }
        ) +
            expandVertically(expandFrom = Alignment.Top) +
            scaleIn(
                // Animate scale from 0f to 1f using the top center as the pivot point.
                transformOrigin = TransformOrigin(0.5f, 0f)
            ) +
            fadeIn(initialAlpha = 0.3f),
    exit = slideOutVertically() + shrinkVertically() + fadeOut() + scaleOut(targetScale = 1.2f)
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold )

the animation used for the slide-in, spring by default.

initialOffsetY: (fullHeight: Int) -> Int = { -it / 2 }

a lambda that takes the full Height of the content and returns the initial offset for the slide-in, by default it returns -fullHeight/2

slideOut

fun slideOut(
    animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold ),
    targetOffset: (fullSize: IntSize) -> IntOffset
): ExitTransition

This slides out the content of the transition, from an offset of IntOffset(0, 0) to the target offset defined in targetOffset. The direction of the slide can be controlled by configuring the targetOffset. A positive x value means sliding from left to right, whereas a negative x value would slide the content from right to left. Similarly, positive and negative y values correspond to sliding down and up, respectively.

If the sliding is only desired horizontally or vertically, instead of along both axis, consider using slideOutHorizontally or slideOutVertically.

targetOffset is a lambda that takes the full size of the content and returns an offset. This allows the offset to be defined proportional to the full size, or as an absolute value.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible,
    enter =
        slideIn(tween(100, easing = LinearOutSlowInEasing)) { fullSize ->
            // Specifies the starting offset of the slide-in to be 1/4 of the width to the
            // right,
            // 100 (pixels) below the content position, which results in a simultaneous slide up
            // and slide left.
            IntOffset(fullSize.width / 4, 100)
        },
    exit =
        slideOut(tween(100, easing = FastOutSlowInEasing)) {
            // The offset can be entirely independent of the size of the content. This specifies
            // a target offset 180 pixels to the left of the content, and 50 pixels below. This
            // will
            // produce a slide-left combined with a slide-down.
            IntOffset(-180, 50)
        },
) {
    // Content that needs to appear/disappear goes here:
    Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
Parameters
animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold )

the animation used for the slide-out, spring by default.

targetOffset: (fullSize: IntSize) -> IntOffset

a lambda that takes the full size of the content and returns the target offset for the slide-out

slideOutHorizontally

fun slideOutHorizontally(
    animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold ),
    targetOffsetX: (fullWidth: Int) -> Int = { -it / 2 }
): ExitTransition

This slides out the content horizontally, from 0 to a target offset defined in targetOffsetX in pixels. The direction of the slide can be controlled by configuring the targetOffsetX. A positive value means sliding to the right, whereas a negative value would slide the content towards the left.

targetOffsetX is a lambda that takes the full width of the content and returns an offset. This allows the target offset to be defined proportional to the full size, or as an absolute value. It defaults to return half of negative width, which would slide the content to the left by half of its width.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOut
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible,
    enter =
        slideInHorizontally(animationSpec = tween(durationMillis = 200)) { fullWidth ->
            // Offsets the content by 1/3 of its width to the left, and slide towards right
            // Overwrites the default animation with tween for this slide animation.
            -fullWidth / 3
        } +
            fadeIn(
                // Overwrites the default animation with tween
                animationSpec = tween(durationMillis = 200)
            ),
    exit =
        slideOutHorizontally(animationSpec = spring(stiffness = Spring.StiffnessHigh)) {
            // Overwrites the ending position of the slide-out to 200 (pixels) to the right
            200
        } + fadeOut()
) {
    // Content that needs to appear/disappear goes here:
    Box(Modifier.fillMaxWidth().requiredHeight(200.dp)) {}
}
Parameters
animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold )

the animation used for the slide-out, spring by default.

targetOffsetX: (fullWidth: Int) -> Int = { -it / 2 }

a lambda that takes the full width of the content and returns the initial offset for the slide-in, by default it returns fullWidth/2

slideOutVertically

fun slideOutVertically(
    animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold ),
    targetOffsetY: (fullHeight: Int) -> Int = { -it / 2 }
): ExitTransition

This slides out the content vertically, from 0 to a target offset defined in targetOffsetY in pixels. The direction of the slide-out can be controlled by configuring the targetOffsetY. A positive target offset means sliding down, whereas a negative value would slide the content up.

targetOffsetY is a lambda that takes the full Height of the content and returns an offset. This allows the target offset to be defined proportional to the full height, or as an absolute value. It defaults to return half of the negative height, which would slide the content up by half of its Height.

Parameters
animationSpec: FiniteAnimationSpec<IntOffset> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold )

the animation used for the slide-out, spring by default.

targetOffsetY: (fullHeight: Int) -> Int = { -it / 2 }

a lambda that takes the full Height of the content and returns the target offset for the slide-out, by default it returns fullHeight/2

splineBasedDecay

fun <T : Any?> splineBasedDecay(density: Density): DecayAnimationSpec<T>

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

AnimatedVisibility

@Composable
fun RowScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandHorizontally(),
    exit: ExitTransition = fadeOut() + shrinkHorizontally(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
): Unit

RowScope.AnimatedVisibility composable animates the appearance and disappearance of its content when the AnimatedVisibility is in a Row. The default animations are tailored specific to the Row layout. See more details below.

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.

The default enter and exit transition is configured based on the horizontal layout of a Row. enter defaults to a combination of fading in and expanding the content horizontally. (The end of the content will be the leading edge as the content expands to its full width.) And exit defaults to shrinking the content horizontally with the end of the content being the leading edge while fading out. The expanding and shrinking will likely also animate the parent and siblings in the row since they rely on the size of appearing/disappearing content.

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. If there's a need to observe the state change of the enter/exit transition and follow up additional action (e.g. remove data, sequential animation, etc), consider the AnimatedVisibility API variant that takes a MutableTransitionState parameter.

Here's an example of using RowScope.AnimatedVisibility in a Row:

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.material.Button
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

var expanded by remember { mutableStateOf(true) }
FloatingActionButton(
    onClick = { expanded = !expanded },
    modifier = Modifier.align(Alignment.CenterHorizontally)
) {
    Row(Modifier.padding(start = 12.dp, end = 12.dp)) {
        Icon(
            Icons.Default.Favorite,
            contentDescription = "Favorite",
            modifier = Modifier.align(Alignment.CenterVertically)
        )
        AnimatedVisibility(expanded, modifier = Modifier.align(Alignment.CenterVertically)) {
            Text(modifier = Modifier.padding(start = 12.dp), text = "Favorite")
        }
    }
}
Spacer(Modifier.requiredHeight(20.dp))
Parameters
visible: Boolean

defines whether the content should be visible

modifier: Modifier = Modifier

modifier for the Layout created to contain the content

enter: EnterTransition = fadeIn() + expandHorizontally()

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

exit: ExitTransition = fadeOut() + shrinkHorizontally()

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

content: @Composable AnimatedVisibilityScope.() -> Unit

Content to appear or disappear based on the value of visible

AnimatedVisibility

@Composable
fun ColumnScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandVertically(),
    exit: ExitTransition = fadeOut() + shrinkVertically(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
): Unit

ColumnScope.AnimatedVisibility composable animates the appearance and disappearance of its content when the AnimatedVisibility is in a Column. The default animations are tailored specific to the Column layout. See more details below.

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.

The default enter and exit transition is configured based on the vertical layout of a Column. enter defaults to a combination of fading in and expanding the content vertically. (The bottom of the content will be the leading edge as the content expands to its full height.) And the exit defaults to shrinking the content vertically with the bottom of the content being the leading edge while fading out. The expanding and shrinking will likely also animate the parent and siblings in the column since they rely on the size of appearing/disappearing content.

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. If there's a need to observe the state change of the enter/exit transition and follow up additional action (e.g. remove data, sequential animation, etc), consider the AnimatedVisibility API variant that takes a MutableTransitionState parameter.

Here's an example of using ColumnScope.AnimatedVisibility in a Column:

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

var itemIndex by remember { mutableStateOf(0) }
val colors = listOf(Color.Red, Color.Green, Color.Blue)
Column(Modifier.fillMaxWidth().clickable { itemIndex = (itemIndex + 1) % colors.size }) {
    colors.forEachIndexed { i, color ->
        // By default ColumnScope.AnimatedVisibility expands and shrinks new content while
        // fading.
        AnimatedVisibility(i <= itemIndex) {
            Box(Modifier.requiredHeight(40.dp).fillMaxWidth().background(color))
        }
    }
}
Parameters
visible: Boolean

defines whether the content should be visible

modifier: Modifier = Modifier

modifier for the Layout created to contain the content

enter: EnterTransition = fadeIn() + expandVertically()

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

exit: ExitTransition = fadeOut() + shrinkVertically()

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 value of visible

AnimatedVisibility

@Composable
fun RowScope.AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,
    modifier: Modifier = Modifier,
    enter: EnterTransition = expandHorizontally() + fadeIn(),
    exit: ExitTransition = shrinkHorizontally() + fadeOut(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
): Unit

RowScope.AnimatedVisibility composable animates the appearance and disappearance of its content as visibleState's targetState changes. The default enter and exit transitions are tailored specific to the Row layout. See more details below. The visibleState can also be used to observe the state of AnimatedVisibility. For example: visibleState.isIdle indicates whether all the animations have finished in AnimatedVisibility, and visibleState.currentState returns the initial state of the current animations.

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.

The default enter and exit transition is configured based on the horizontal layout of a Row. enter defaults to a combination of fading in and expanding the content horizontally. (The end of the content will be the leading edge as the content expands to its full width.) And exit defaults to shrinking the content horizontally with the end of the content being the leading edge while fading out. The expanding and shrinking will likely also animate the parent and siblings in the row since they rely on the size of appearing/disappearing content.

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. Both currentState and targetState will be false for visibleState.

Parameters
visibleState: MutableTransitionState<Boolean>

defines whether the content should be visible

modifier: Modifier = Modifier

modifier for the Layout created to contain the content

enter: EnterTransition = expandHorizontally() + fadeIn()

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

exit: ExitTransition = shrinkHorizontally() + 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 value of visibleState

AnimatedVisibility

@Composable
fun ColumnScope.AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,
    modifier: Modifier = Modifier,
    enter: EnterTransition = expandVertically() + fadeIn(),
    exit: ExitTransition = shrinkVertically() + fadeOut(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
): Unit

ColumnScope.AnimatedVisibility composable animates the appearance and disappearance of its content as visibleState's targetState changes. The default enter and exit transitions are tailored specific to the Column layout. See more details below. The visibleState can also be used to observe the state of AnimatedVisibility. For example: visibleState.isIdle indicates whether all the animations have finished in AnimatedVisibility, and visibleState.currentState returns the initial state of the current animations.

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.

The default enter and exit transition is configured based on the vertical layout of a Column. enter defaults to a combination of fading in and expanding the content vertically. (The bottom of the content will be the leading edge as the content expands to its full height.) And the exit defaults to shrinking the content vertically with the bottom of the content being the leading edge while fading out. The expanding and shrinking will likely also animate the parent and siblings in the column since they rely on the size of appearing/disappearing content.

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. Both currentState and targetState will be false for visibleState.

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

var visible by remember { mutableStateOf(true) }
val colors = remember { listOf(Color(0xff2a9d8f), Color(0xffe9c46a), Color(0xfff4a261)) }
Column {
    repeat(3) {
        AnimatedVisibility(
            visibleState =
                remember {
                        // This sets up the initial state of the AnimatedVisibility to false to
                        // guarantee an initial enter transition. In contrast, initializing this
                        // as
                        // `MutableTransitionState(visible)` would result in no initial enter
                        // transition.
                        MutableTransitionState(initialState = false)
                    }
                    .apply {
                        // This changes the target state of the visible state. If it's different
                        // than
                        // the initial state, an enter/exit transition will be triggered.
                        targetState = visible
                    },
        ) { // Content that needs to appear/disappear goes here:
            Box(Modifier.fillMaxWidth().height(100.dp).background(colors[it]))
        }
    }
}
Parameters
visibleState: MutableTransitionState<Boolean>

defines whether the content should be visible

modifier: Modifier = Modifier

modifier for the Layout created to contain the content

enter: EnterTransition = expandVertically() + fadeIn()

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

exit: ExitTransition = shrinkVertically() + 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 of visibleState

Crossfade

@ExperimentalAnimationApi
@Composable
fun <T : Any?> Transition<T>.Crossfade(
    modifier: Modifier = Modifier,
    animationSpec: FiniteAnimationSpec<Float> = tween(),
    contentKey: (targetState) -> Any? = { it },
    content: @Composable (targetState) -> Unit
): Unit

Crossfade allows to switch between two layouts with a crossfade animation. The target state of this Crossfade will be the target state of the given Transition object. In other words, when the Transition changes target, the Crossfade will fade in the target content while fading out the current content.

content is a mapping between the state and the composable function for the content of that state. During the crossfade, content lambda will be invoked multiple times with different state parameter such that content associated with different states will be fading in/out at the same time.

contentKey will be used to perform equality check for different states. For example, when two states resolve to the same content key, there will be no animation for that state change. By default, contentKey is the same as the state object. contentKey can be particularly useful if target state object gets recreated across save & restore while a more persistent key is needed to properly restore the internal states of the content.

Parameters
modifier: Modifier = Modifier

Modifier to be applied to the animation container.

animationSpec: FiniteAnimationSpec<Float> = tween()

the AnimationSpec to configure the animation.

animateBounds

@ExperimentalSharedTransitionApi
fun Modifier.animateBounds(
    lookaheadScope: LookaheadScope,
    modifier: Modifier = Modifier,
    boundsTransform: BoundsTransform = DefaultBoundsTransform,
    animateMotionFrameOfReference: Boolean = false
): Modifier

Modifier to animate layout changes (position and/or size) that occur within a LookaheadScope.

So, the given lookaheadScope defines the coordinate space considered to trigger an animation. For example, if lookaheadScope was defined at the root of the app hierarchy, then any layout changes visible within the screen will trigger an animation, if it, in contrast was defined within a scrolling parent, then, as long the LookaheadScope scrolls with is content, no animation will be triggered, as there will be no changes within its coordinate space.

The animation is driven with a FiniteAnimationSpec produced by the given BoundsTransform function, which you may use to customize the animations based on the initial and target bounds.

Do note that certain Layout Modifiers when chained with animateBounds, may only cause an immediate observable change to either the child or the parent Layout which can result in undesired behavior. For those cases you can instead provide it to the modifier parameter. This allows animateBounds to envelop the size and constraints change and propagate them gradually to both its parent and child Layout.

You may see the difference when supplying a Layout Modifier in modifier on the following example:

import androidx.compose.animation.BoundsTransform
import androidx.compose.animation.animateBounds
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.shape.RoundedCornerShape
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.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round

// Example showing the difference between providing a Layout Modifier as a parameter of
// `animateBounds` and chaining the Layout Modifier.

// We use `padding` in this example, as it provides an immediate change in layout to its child,
// but not the parent, which sees the same resulting layout. The difference can be seen in the
// Text (content under padding) and an accompanying Cyan Box (a sibling, under the same Row
// parent).
LookaheadScope {
    val boundsTransform = remember {
        BoundsTransform { _, _ ->
            spring(stiffness = 50f, visibilityThreshold = Rect.VisibilityThreshold)
        }
    }

    var toggleAnimation by remember { mutableStateOf(true) }

    Column(Modifier.clickable { toggleAnimation = !toggleAnimation }) {
        Text(
            "See the difference in animation when the Layout Modifier is a parameter of animateBounds. Padding, in this example."
        )
        Spacer(Modifier.height(12.dp))
        Text("Layout Modifier as a parameter.")
        Row(Modifier.fillMaxWidth()) {
            Box(
                Modifier.animateBounds(
                        lookaheadScope = this@LookaheadScope,
                        modifier =
                            // By providing this Modifier as a parameter of `animateBounds`,
                            // both content and parent see a gradual/animated change in Layout.
                            Modifier.padding(
                                horizontal = if (toggleAnimation) 10.dp else 50.dp
                            ),
                        boundsTransform = boundsTransform
                    )
                    .background(Color.Red, RoundedCornerShape(12.dp))
                    .height(50.dp)
            ) {
                Text("Layout Content", Modifier.align(Alignment.Center))
            }
            Box(Modifier.size(50.dp).background(Color.Cyan, RoundedCornerShape(12.dp)))
        }
        Spacer(Modifier.height(12.dp))
        Text("Layout Modifier after AnimateBounds.")
        Row(Modifier.fillMaxWidth()) {
            Box(
                Modifier.animateBounds(
                        lookaheadScope = this@LookaheadScope,
                        boundsTransform = boundsTransform
                    )
                    // The content is able to animate the change in padding, but since the
                    // parent Layout sees no difference, the change in position is immediate.
                    .padding(horizontal = if (toggleAnimation) 10.dp else 50.dp)
                    .background(Color.Red, RoundedCornerShape(12.dp))
                    .height(50.dp)
            ) {
                Text("Layout Content", Modifier.align(Alignment.Center))
            }
            Box(Modifier.size(50.dp).background(Color.Cyan, RoundedCornerShape(12.dp)))
        }
        Spacer(Modifier.height(12.dp))
        Text("Layout Modifier before AnimateBounds.")
        Row(Modifier.fillMaxWidth()) {
            Box(
                Modifier
                    // The parent is able to see the change in position and the animated size,
                    // so it can smoothly place both its children, but the content of the Box
                    // cannot see the gradual changes so it remains constant.
                    .padding(horizontal = if (toggleAnimation) 10.dp else 50.dp)
                    .animateBounds(
                        lookaheadScope = this@LookaheadScope,
                        boundsTransform = boundsTransform
                    )
                    .background(Color.Red, RoundedCornerShape(12.dp))
                    .height(50.dp)
            ) {
                Text("Layout Content", Modifier.align(Alignment.Center))
            }
            Box(Modifier.size(50.dp).background(Color.Cyan, RoundedCornerShape(12.dp)))
        }
    }
}

By default, changes in position under LayoutCoordinates.introducesMotionFrameOfReference are excluded from the animation and are instead immediately applied, as they are expected to be frequent/continuous (to handle Layouts under Scroll). You may change this behavior by passing animateMotionFrameOfReference as true. Keep in mind, doing that under a scroll may result in the Layout "chasing" the scroll offset, as it will constantly animate to the latest position.

A basic use-case is animating a layout based on content changes, such as the String changing on a Text:

import androidx.compose.animation.animateBounds
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.layout.LookaheadScope
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round

// Example where the change in content triggers the layout change on the item with animateBounds
val textShort = remember { "Foo ".repeat(10) }
val textLong = remember { "Bar ".repeat(50) }

var toggle by remember { mutableStateOf(true) }

LookaheadScope {
    Box(
        modifier = Modifier.fillMaxSize().clickable { toggle = !toggle },
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = if (toggle) textShort else textLong,
            modifier =
                Modifier.fillMaxWidth(0.7f)
                    .background(Color.LightGray)
                    .animateBounds(this@LookaheadScope)
                    .padding(10.dp),
        )
    }
}

It also provides an easy way to animate layout changes of a complex Composable Layout:

import androidx.compose.animation.animateBounds
import androidx.compose.animation.animateContentSize
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.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.compose.ui.util.fastForEach

var itemRowCount by remember { mutableIntStateOf(1) }
val colors = remember { listOf(Color.Cyan, Color.Magenta, Color.Yellow, Color.Green) }

// A case showing `animateBounds` being used to animate layout changes driven by a parent Layout
LookaheadScope {
    Column(Modifier.clickable { itemRowCount = if (itemRowCount != 2) 2 else 1 }) {
        Text("Click to toggle animation.")
        FlowRow(
            modifier =
                Modifier.fillMaxWidth()
                    // Note that the wrap content size changes for FlowRow as the content
                    // adjusts
                    // to one or two lines, we can simply use `animateContentSize()` to make
                    // sure
                    // all items are visible during their animation.
                    .animateContentSize(),
            // Try changing the arrangement as well!
            horizontalArrangement = Arrangement.spacedBy(8.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp),
            // We use the maxItems parameter to change the layout of the FlowRow at different
            // states
            maxItemsInEachRow = itemRowCount
        ) {
            colors.fastForEach {
                Box(
                    Modifier.animateBounds(this@LookaheadScope)
                        // Note the modifier order, we declare the background after
                        // `animateBounds` to make sure it animates with the rest of the content
                        .background(it, RoundedCornerShape(12.dp))
                        .weight(weight = 1f, fill = true)
                        .height(100.dp)
                )
            }
        }
    }
}

Since BoundsTransform is called when initiating an animation, you may also use it to calculate a keyframe based animation:

import androidx.compose.animation.BoundsTransform
import androidx.compose.animation.animateBounds
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.keyframesWithSpline
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
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.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round

var toggle by remember { mutableStateOf(true) }

// Example using BoundsTransform to calculate an animation using keyframes with splines.
LookaheadScope {
    Box(Modifier.fillMaxSize().clickable { toggle = !toggle }) {
        Text(
            text = "Hello, World!",
            textAlign = TextAlign.Center,
            modifier =
                Modifier.align(if (toggle) Alignment.TopStart else Alignment.TopEnd)
                    .animateBounds(
                        lookaheadScope = this@LookaheadScope,
                        boundsTransform = { initialBounds, targetBounds ->
                            // We'll use a keyframe to emphasize the animation in position and
                            // size.
                            keyframesWithSpline {
                                durationMillis = 1200

                                // Emphasize with an increase in size
                                val size = targetBounds.size.times(2f)

                                // Emphasize the path with a slight curve at the halfway point
                                val position =
                                    targetBounds.topLeft
                                        .plus(initialBounds.topLeft)
                                        .times(0.5f)
                                        .plus(
                                            Offset(
                                                // Consider the increase in size (from the
                                                // center,
                                                // to keep the Layout aligned at the keyframe)
                                                x = -(size.width - targetBounds.width) * 0.5f,
                                                // Emphasize the path with a vertical offset
                                                y = size.height * 0.5f
                                            )
                                        )

                                // Only need to define the intermediate keyframe, initial and
                                // target are implicit.
                                Rect(position, size).atFraction(0.5f).using(LinearEasing)
                            }
                        }
                    )
                    .background(Color.LightGray, RoundedCornerShape(50))
                    .padding(10.dp)
                    // Text is laid out with the animated fixed Constraints, relax constraints
                    // back to wrap content to be able to center Align vertically.
                    .wrapContentSize(Alignment.Center)
        )
    }
}

It may also be used together with movableContent as long as the given LookaheadScope is in a common place within the Layout hierarchy of the slots presenting the movableContent:

import androidx.compose.animation.animateBounds
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.movableContentWithReceiverOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round

// Example showing how to animate a Layout that can be presented on different Layout Composables
// as the state changes using `movableContent`.
var position by remember { mutableIntStateOf(-1) }

val movableContent = remember {
    // To animate a Layout that can be presented in different Composables, we can use
    // `animateBounds` with `movableContent`.
    movableContentWithReceiverOf<LookaheadScope> {
        Box(
            Modifier.animateBounds(
                    lookaheadScope = this@movableContentWithReceiverOf,
                    boundsTransform = { _, _ ->
                        spring(
                            dampingRatio = Spring.DampingRatioLowBouncy,
                            stiffness = Spring.StiffnessVeryLow,
                            visibilityThreshold = Rect.VisibilityThreshold
                        )
                    }
                )
                // Our movableContent can always fill its container in this example.
                .fillMaxSize()
                .background(Color.Cyan, RoundedCornerShape(8.dp))
        )
    }
}

LookaheadScope {
    Box(Modifier.fillMaxSize()) {
        // Initial container of our Layout, at the center of the screen.
        Box(
            Modifier.size(200.dp)
                .border(3.dp, Color.Red, RoundedCornerShape(8.dp))
                .align(Alignment.Center)
                .clickable { position = -1 }
        ) {
            if (position < 0) {
                movableContent()
            }
        }

        repeat(4) { index ->
            // Four additional Boxes where our content may be move to.
            Box(
                Modifier.size(100.dp)
                    .border(2.dp, Color.Blue, RoundedCornerShape(8.dp))
                    .align { size, space, _ ->
                        val horizontal = if (index % 2 == 0) 0.15f else 0.85f
                        val vertical = if (index < 2) 0.15f else 0.85f

                        Offset(
                                x = (space.width - size.width) * horizontal,
                                y = (space.height - size.height) * vertical
                            )
                            .round()
                    }
                    .clickable { position = index }
            ) {
                if (position == index) {
                    // The call to movable content will trigger `Modifier.animateBounds()` to
                    // animate the content's position and size from its previous state.
                    movableContent()
                }
            }
        }
    }
}
Parameters
lookaheadScope: LookaheadScope

The scope from which this animateBounds will calculate its animations from. This implies that as long as you're expecting an animation the reference of the given LookaheadScope shouldn't change, otherwise you may get unexpected behavior.

modifier: Modifier = Modifier

Optional intermediate Modifier, may be used in cases where otherwise immediate layout changes are perceived as gradual by both the parent and child Layout.

boundsTransform: BoundsTransform = DefaultBoundsTransform

Produce a customized FiniteAnimationSpec based on the initial and target bounds, called when an animation is triggered.

animateMotionFrameOfReference: Boolean = false

When true, changes under LayoutCoordinates.introducesMotionFrameOfReference (for continuous positional changes, such as Scroll Offset) are included when calculating an animation. false by default, where the changes are instead applied directly into the layout without triggering an animation.

animateColor

@Composable
inline fun <S : Any?> Transition<S>.animateColor(
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Color> = { spring() },
    label: String = "ColorAnimation",
    targetValueByState: @Composable (state) -> Color
): State<Color>

Creates a Color animation as a part of the given Transition. This means the lifecycle of this animation will be managed by the Transition.

targetValueByState is used as a mapping from a target state to the target value of this animation. Transition will be using this mapping to determine what value to target this animation towards. Note that targetValueByState is a composable function. This means the mapping function could access states, CompositionLocals, themes, etc. If the target value changes when the Transition already reached its targetState, the Transition will run an animation to ensure the new target value is reached smoothly.

An optional transitionSpec can be provided to specify (potentially different) animations for each pair of initialState and targetState. FiniteAnimationSpec can be used to describe such animations, such as tween, spring, keyframes and even repeatable, but not infiniteRepeatable. By default, transitionSpec uses a spring animation for all transition destinations.

label is used to differentiate from other animations in the same transition in Android Studio.

import androidx.compose.animation.animateColor
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp

// enum class ComponentState { Pressed, Released }
var useRed by remember { mutableStateOf(false) }
var toState by remember { mutableStateOf(ComponentState.Released) }
val modifier =
    Modifier.pointerInput(Unit) {
        detectTapGestures(
            onPress = {
                toState = ComponentState.Pressed
                tryAwaitRelease()
                toState = ComponentState.Released
            }
        )
    }

// Defines a transition of `ComponentState`, and updates the transition when the provided
// [targetState] changes. The tran
// sition will run all of the child animations towards the new
// [targetState] in response to the [targetState] change.
val transition: Transition<ComponentState> = updateTransition(targetState = toState)
// Defines a float animation as a child animation the transition. The current animation value
// can be read from the returned State<Float>.
val scale: Float by
    transition.animateFloat(
        // Defines a transition spec that uses the same low-stiffness spring for *all*
        // transitions of this float, no matter what the target is.
        transitionSpec = { spring(stiffness = 50f) }
    ) { state ->
        // This code block declares a mapping from state to value.
        if (state == ComponentState.Pressed) 3f else 1f
    }

// Defines a color animation as a child animation of the transition.
val color: Color by
    transition.animateColor(
        transitionSpec = {
            when {
                ComponentState.Pressed isTransitioningTo ComponentState.Released ->
                    // Uses spring for the transition going from pressed to released
                    spring(stiffness = 50f)
                else ->
                    // Uses tween for all the other transitions. (In this case there is
                    // only one other transition. i.e. released -> pressed.)
                    tween(durationMillis = 500)
            }
        }
    ) { state ->
        when (state) {
            // Similar to the float animation, we need to declare the target values
            // for each state. In this code block we can access theme colors.
            ComponentState.Pressed -> MaterialTheme.colors.primary
            // We can also have the target value depend on other mutableStates,
            // such as `useRed` here. Whenever the target value changes, transition
            // will automatically animate to the new value even if it has already
            // arrived at its target state.
            ComponentState.Released -> if (useRed) Color.Red else MaterialTheme.colors.secondary
        }
    }
Column {
    Button(
        modifier = Modifier.padding(10.dp).align(Alignment.CenterHorizontally),
        onClick = { useRed = !useRed }
    ) {
        Text("Change Color")
    }
    Box(
        modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.Center)
            .size((100 * scale).dp)
            .background(color)
    )
}
Returns
State<Color>

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

@Composable
fun InfiniteTransition.animateColor(
    initialValue: Color,
    targetValue: Color,
    animationSpec: InfiniteRepeatableSpec<Color>,
    label: String = "ColorAnimation"
): State<Color>

Creates a Color animation that runs infinitely as a part of the given InfiniteTransition.

Once the animation is created, it will run from initialValue to targetValue and repeat. Depending on the RepeatMode of the provided animationSpec, the animation could either restart after each iteration (i.e. RepeatMode.Restart), or reverse after each iteration (i.e . RepeatMode.Reverse).

If initialValue or targetValue is changed at any point during the animation, the animation will be restarted with the new initial/targetValue. Note: this means animation continuity will not be preserved when changing either initialValue or targetValue.

A label for differentiating this animation from others in android studio.

import androidx.compose.animation.animateColor
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.graphics.graphicsLayer

@Composable
fun InfinitelyPulsingHeart() {
    // Creates an [InfiniteTransition] instance for managing child animations.
    val infiniteTransition = rememberInfiniteTransition()

    // Creates a child animation of float type as a part of the [InfiniteTransition].
    val scale by
        infiniteTransition.animateFloat(
            initialValue = 3f,
            targetValue = 6f,
            animationSpec =
                infiniteRepeatable(
                    // Infinitely repeating a 1000ms tween animation using default easing curve.
                    animation = tween(1000),
                    // After each iteration of the animation (i.e. every 1000ms), the animation
                    // will
                    // start again from the [initialValue] defined above.
                    // This is the default [RepeatMode]. See [RepeatMode.Reverse] below for an
                    // alternative.
                    repeatMode = RepeatMode.Restart
                )
        )

    // Creates a Color animation as a part of the [InfiniteTransition].
    val color by
        infiniteTransition.animateColor(
            initialValue = Color.Red,
            targetValue = Color(0xff800000), // Dark Red
            animationSpec =
                infiniteRepeatable(
                    // Linearly interpolate between initialValue and targetValue every 1000ms.
                    animation = tween(1000, easing = LinearEasing),
                    // Once [TargetValue] is reached, starts the next iteration in reverse (i.e.
                    // from
                    // TargetValue to InitialValue). Then again from InitialValue to
                    // TargetValue. This
                    // [RepeatMode] ensures that the animation value is *always continuous*.
                    repeatMode = RepeatMode.Reverse
                )
        )

    Box(Modifier.fillMaxSize()) {
        Icon(
            Icons.Filled.Favorite,
            contentDescription = null,
            modifier =
                Modifier.align(Alignment.Center).graphicsLayer(scaleX = scale, scaleY = scale),
            tint = color
        )
    }
}

animateContentSize

fun Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ),
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
): Modifier

This modifier animates its own size when its child modifier (or the child composable if it is already at the tail of the chain) changes size. This allows the parent modifier to observe a smooth size change, resulting in an overall continuous visual change.

A FiniteAnimationSpec can be optionally specified for the size change animation. By default, spring will be used.

An optional finishedListener can be supplied to get notified when the size change animation is finished. Since the content size change can be dynamic in many cases, both initial value and target value (i.e. final size) will be passed to the finishedListener. Note: if the animation is interrupted, the initial value will be the size at the point of interruption. This is intended to help determine the direction of the size change (i.e. expand or collapse in x and y dimensions).

import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

val shortText = "Hi"
val longText = "Very long text\nthat spans across\nmultiple lines"
var short by remember { mutableStateOf(true) }
Box(
    modifier =
        Modifier.background(Color.Blue, RoundedCornerShape(15.dp))
            .clickable { short = !short }
            .padding(20.dp)
            .wrapContentSize()
            .animateContentSize()
) {
    Text(
        if (short) {
            shortText
        } else {
            longText
        },
        style = LocalTextStyle.current.copy(color = Color.White)
    )
}
Parameters
animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold )

a finite animation that will be used to animate size change, spring by default

finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null

an optional listener to be called when the content change animation is completed.

animateContentSize

fun Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold ),
    alignment: Alignment = Alignment.TopStart,
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
): Modifier

This modifier animates its own size when its child modifier (or the child composable if it is already at the tail of the chain) changes size. This allows the parent modifier to observe a smooth size change, resulting in an overall continuous visual change.

A FiniteAnimationSpec can be optionally specified for the size change animation. By default, spring will be used.

An optional finishedListener can be supplied to get notified when the size change animation is finished. Since the content size change can be dynamic in many cases, both initial value and target value (i.e. final size) will be passed to the finishedListener. Note: if the animation is interrupted, the initial value will be the size at the point of interruption. This is intended to help determine the direction of the size change (i.e. expand or collapse in x and y dimensions).

import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

val shortText = "Hi"
val longText = "Very long text\nthat spans across\nmultiple lines"
var short by remember { mutableStateOf(true) }
Box(
    modifier =
        Modifier.background(Color.Blue, RoundedCornerShape(15.dp))
            .clickable { short = !short }
            .padding(20.dp)
            .wrapContentSize()
            .animateContentSize()
) {
    Text(
        if (short) {
            shortText
        } else {
            longText
        },
        style = LocalTextStyle.current.copy(color = Color.White)
    )
}
Parameters
animationSpec: FiniteAnimationSpec<IntSize> = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold )

a finite animation that will be used to animate size change, spring by default

alignment: Alignment = Alignment.TopStart

sets the alignment of the content during the animation. Alignment.TopStart by default.

finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null

an optional listener to be called when the content change animation is completed.

togetherWith

infix fun EnterTransition.togetherWith(exit: ExitTransition): ContentTransform

This creates a ContentTransform using the provided EnterTransition and exit, where the enter and exit transition will be running simultaneously. For example:

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.ui.unit.IntSize

// enum class CartState { Expanded, Collapsed }
val transitionSpec: AnimatedContentTransitionScope<CartState>.() -> ContentTransform = {
    // Fade in with a delay so that it starts after fade out
    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
                    }
                }
            }
        )
}

with

@ExperimentalAnimationApi
infix fun EnterTransition.with(exit: ExitTransition): ContentTransform

Extension properties

VectorConverter

val Color.Companion.VectorConverter: (colorSpace: ColorSpace) -> TwoWayConverter<ColorAnimationVector4D>

A lambda that takes a ColorSpace and returns a converter that can both convert a Color to a AnimationVector4D, and convert a AnimationVector4D) back to a Color in the given ColorSpace.