Quick guide to Animations in Compose

Compose has many built-in animation mechanisms and it can be overwhelming to know which one to choose. Below is a list of common animation use cases. For more detailed information about the full set of different API options available to you, read the full Compose Animation documentation.

Animate common composable properties

Compose provides convenient APIs that allow you to solve for many common animation use cases. This section demonstrates how you can animate common properties of a composable.

Animate appearing / disappearing

Figure 1. Animating the appearance and disappearance of an item in a Column

Use AnimatedVisibility to hide or show a Composable. Children inside AnimatedVisibility can use Modifier.animateEnterExit() for their own enter or exit transition.

varvisiblebyremember{
mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(visible){
// your composable here
// ...
}

The enter and exit parameters of AnimatedVisibility allow you to configure how a composable behaves when it appears and disappears. Read the full documentation for more information.

Another option for animating the visibility of a composable is to animate the alpha over time using animateFloatAsState:

varvisiblebyremember{
mutableStateOf(true)
}
valanimatedAlphabyanimateFloatAsState(
targetValue=if(visible)1.0felse0f,
label="alpha"
)
Box(
modifier=Modifier
.size(200.dp)
.graphicsLayer{
alpha=animatedAlpha
}
.clip(RoundedCornerShape(8.dp))
.background(colorGreen)
.align(Alignment.TopCenter)
){
}

However, changing the alpha comes with the caveat that the composable remains in the composition and continues to occupy the space it's laid out in. This could cause screen readers and other accessibility mechanisms to still consider the item on screen. On the other hand, AnimatedVisibility eventually removes the item from the composition.

Figure 2. Animating the alpha of a composable

Animate background color

Figure 3. Animating background color of composable

valanimatedColorbyanimateColorAsState(
if(animateBackgroundColor)colorGreenelsecolorBlue,
label="color"
)
Column(
modifier=Modifier.drawBehind{
drawRect(animatedColor)
}
){
// your composable here
}

This option is more performant than using Modifier.background(). Modifier.background() is acceptable for a one-shot color setting, but when animating a color over time, this could cause more recompositions than necessary.

For infinitely animating the background color, see repeating an animation section.

Animate the size of a composable

Figure 4. Composable smoothly animating between a small and a larger size

Compose lets you animate the size of composables in a few different ways. Use animateContentSize() for animations between composable size changes.

For example, if you have a box that contains text which can expand from one to multiple lines you can use Modifier.animateContentSize() to achieve a smoother transition:

varexpandedbyremember{mutableStateOf(false)}
Box(
modifier=Modifier
.background(colorBlue)
.animateContentSize()
.height(if(expanded)400.dpelse200.dp)
.fillMaxWidth()
.clickable(
interactionSource=remember{MutableInteractionSource()},
indication=null
){
expanded=!expanded
}
){
}

You can also use AnimatedContent, with a SizeTransform to describe how size changes should take place.

Animate position of composable

Figure 5. Composable moving by an offset

To animate the position of a composable, use Modifier.offset{ } combined with animateIntOffsetAsState().

varmovedbyremember{mutableStateOf(false)}
valpxToMove=with(LocalDensity.current){
100.dp.toPx().roundToInt()
}
valoffsetbyanimateIntOffsetAsState(
targetValue=if(moved){
IntOffset(pxToMove,pxToMove)
}else{
IntOffset.Zero
},
label="offset"
)

Box(
modifier=Modifier
.offset{
offset
}
.background(colorBlue)
.size(100.dp)
.clickable(
interactionSource=remember{MutableInteractionSource()},
indication=null
){
moved=!moved
}
)

If you want to ensure that composables are not drawn over or under other composables when animating position or size, use Modifier.layout{ }. This modifier propagates size and position changes to the parent, which then affects other children.

For example, if you are moving a Box within a Column and the other children need to move when the Box moves, include the offset information with Modifier.layout{ } as follows:

vartoggledbyremember{
mutableStateOf(false)
}
valinteractionSource=remember{
MutableInteractionSource()
}
Column(
modifier=Modifier
.padding(16.dp)
.fillMaxSize()
.clickable(indication=null,interactionSource=interactionSource){
toggled=!toggled
}
){
valoffsetTarget=if(toggled){
IntOffset(150,150)
}else{
IntOffset.Zero
}
valoffset=animateIntOffsetAsState(
targetValue=offsetTarget,label="offset"
)
Box(
modifier=Modifier
.size(100.dp)
.background(colorBlue)
)
Box(
modifier=Modifier
.layout{measurable,constraints->
valoffsetValue=if(isLookingAhead)offsetTargetelseoffset.value
valplaceable=measurable.measure(constraints)
layout(placeable.width+offsetValue.x,placeable.height+offsetValue.y){
placeable.placeRelative(offsetValue)
}
}
.size(100.dp)
.background(colorGreen)
)
Box(
modifier=Modifier
.size(100.dp)
.background(colorBlue)
)
}

Figure 6. Animating with Modifier.layout{ }

Animate padding of a composable

Figure 7. Composable with its padding animating

To animate the padding of a composable, use animateDpAsState combined with Modifier.padding():

vartoggledbyremember{
mutableStateOf(false)
}
valanimatedPaddingbyanimateDpAsState(
if(toggled){
0.dp
}else{
20.dp
},
label="padding"
)
Box(
modifier=Modifier
.aspectRatio(1f)
.fillMaxSize()
.padding(animatedPadding)
.background(Color(0xff53D9A1))
.clickable(
interactionSource=remember{MutableInteractionSource()},
indication=null
){
toggled=!toggled
}
)

Animate elevation of a composable

Figure 8. Composable's elevation animating on click

To animate the elevation of a composable, use animateDpAsState combined with Modifier.graphicsLayer{ }. For once-off elevation changes, use Modifier.shadow(). If you are animating the shadow, using Modifier.graphicsLayer{ } modifier is the more performant option.

valmutableInteractionSource=remember{
MutableInteractionSource()
}
valpressed=mutableInteractionSource.collectIsPressedAsState()
valelevation=animateDpAsState(
targetValue=if(pressed.value){
32.dp
}else{
8.dp
},
label="elevation"
)
Box(
modifier=Modifier
.size(100.dp)
.align(Alignment.Center)
.graphicsLayer{
this.shadowElevation=elevation.value.toPx()
}
.clickable(interactionSource=mutableInteractionSource,indication=null){
}
.background(colorGreen)
){
}

Alternatively, use the Card composable, and set the elevation property to different values per state.

Animate text scale, translation or rotation

Figure 9. Text animating smoothly between two sizes

When animating scale, translation, or rotation of text, set the textMotion parameter on TextStyle to TextMotion.Animated. This ensures smoother transitions between text animations. Use Modifier.graphicsLayer{ } to translate, rotate or scale the text.

valinfiniteTransition=rememberInfiniteTransition(label="infinite transition")
valscalebyinfiniteTransition.animateFloat(
initialValue=1f,
targetValue=8f,
animationSpec=infiniteRepeatable(tween(1000),RepeatMode.Reverse),
label="scale"
)
Box(modifier=Modifier.fillMaxSize()){
Text(
text="Hello",
modifier=Modifier
.graphicsLayer{
scaleX=scale
scaleY=scale
transformOrigin=TransformOrigin.Center
}
.align(Alignment.Center),
// Text composable does not take TextMotion as a parameter.
// Provide it via style argument but make sure that we are copying from current theme
style=LocalTextStyle.current.copy(textMotion=TextMotion.Animated)
)
}

Animate text color

Figure 10. Example showing animating text color

To animate text color, use the color lambda on the BasicText composable:

valinfiniteTransition=rememberInfiniteTransition(label="infinite transition")
valanimatedColorbyinfiniteTransition.animateColor(
initialValue=Color(0xFF60DDAD),
targetValue=Color(0xFF4285F4),
animationSpec=infiniteRepeatable(tween(1000),RepeatMode.Reverse),
label="color"
)
BasicText(
text="Hello Compose",
color={
animatedColor
},
// ...
)

Switch between different types of content

Figure 11. Using AnimatedContent to animate changes between different composables (slowed down)

Use AnimatedContent to animate between different composables, if you just want a standard fade between composables, use Crossfade.

varstatebyremember{
mutableStateOf(UiState.Loading)
}
AnimatedContent(
state,
transitionSpec={
fadeIn(
animationSpec=tween(3000)
)togetherWithfadeOut(animationSpec=tween(3000))
},
modifier=Modifier.clickable(
interactionSource=remember{MutableInteractionSource()},
indication=null
){
state=when(state){
UiState.Loading->UiState.Loaded
UiState.Loaded->UiState.Error
UiState.Error->UiState.Loading
}
},
label="Animated Content"
){targetState->
when(targetState){
UiState.Loading->{
LoadingScreen()
}
UiState.Loaded->{
LoadedScreen()
}
UiState.Error->{
ErrorScreen()
}
}
}

AnimatedContent can be customized to show many different kinds of enter and exit transitions. For more information, read the documentation on AnimatedContent or read this blog post on AnimatedContent.

Animate whilst navigating to different destinations

Figure 12. Animating between composables using navigation-compose

To animate transitions between composables when using the navigation-compose artifact, specify the enterTransition and exitTransition on a composable. You can also set the default animation to be used for all destinations at the top level NavHost:

valnavController=rememberNavController()
NavHost(
navController=navController,startDestination="landing",
enterTransition={EnterTransition.None},
exitTransition={ExitTransition.None}
){
composable("landing"){
ScreenLanding(
// ...
)
}
composable(
"detail/{photoUrl}",
arguments=listOf(navArgument("photoUrl"){type=NavType.StringType}),
enterTransition={
fadeIn(
animationSpec=tween(
300,easing=LinearEasing
)
)+slideIntoContainer(
animationSpec=tween(300,easing=EaseIn),
towards=AnimatedContentTransitionScope.SlideDirection.Start
)
},
exitTransition={
fadeOut(
animationSpec=tween(
300,easing=LinearEasing
)
)+slideOutOfContainer(
animationSpec=tween(300,easing=EaseOut),
towards=AnimatedContentTransitionScope.SlideDirection.End
)
}
){backStackEntry->
ScreenDetails(
// ...
)
}
}

There are many different kinds of enter and exit transitions that apply different effects to the incoming and outgoing content, see the documentation for more.

Repeat an animation

Figure 13. Background color animating between two values, infinitely

Use rememberInfiniteTransition with an infiniteRepeatable animationSpec to continuously repeat your animation. Change RepeatModes to specify how it should go back and forth.

Use repeatable to repeat a set number of times.

valinfiniteTransition=rememberInfiniteTransition(label="infinite")
valcolorbyinfiniteTransition.animateColor(
initialValue=Color.Green,
targetValue=Color.Blue,
animationSpec=infiniteRepeatable(
animation=tween(1000,easing=LinearEasing),
repeatMode=RepeatMode.Reverse
),
label="color"
)
Column(
modifier=Modifier.drawBehind{
drawRect(color)
}
){
// your composable here
}

Start an animation on launch of a composable

LaunchedEffect runs when a composable enters the composition. It starts an animation on launch of a composable, you can use this to drive the animation state change. Using Animatable with the animateTo method to start the animation on launch:

valalphaAnimation=remember{
Animatable(0f)
}
LaunchedEffect(Unit){
alphaAnimation.animateTo(1f)
}
Box(
modifier=Modifier.graphicsLayer{
alpha=alphaAnimation.value
}
)

Create sequential animations

Figure 14. Diagram indicating how a sequential animation progresses, one by one.

Use the Animatable coroutine APIs to perform sequential or concurrent animations. Calling animateTo on the Animatable one after the other causes each animation to wait for the previous animations to finish before proceeding . This is because it is a suspend function.

valalphaAnimation=remember{Animatable(0f)}
valyAnimation=remember{Animatable(0f)}
LaunchedEffect("animationKey"){
alphaAnimation.animateTo(1f)
yAnimation.animateTo(100f)
yAnimation.animateTo(500f,animationSpec=tween(100))
}

Create concurrent animations

Figure 15. Diagram indicating how concurrent animations progress, all at the same time.

Use the coroutine APIs (Animatable#animateTo() or animate), or the Transition API to achieve concurrent animations. If you use multiple launch functions in a coroutine context, it launches the animations at the same time:

valalphaAnimation=remember{Animatable(0f)}
valyAnimation=remember{Animatable(0f)}
LaunchedEffect("animationKey"){
launch{
alphaAnimation.animateTo(1f)
}
launch{
yAnimation.animateTo(100f)
}
}

You could use the updateTransition API to use the same state to drive many different property animations at the same time. The example below animates two properties controlled by a state change, rect and borderWidth:

varcurrentStatebyremember{mutableStateOf(BoxState.Collapsed)}
valtransition=updateTransition(currentState,label="transition")
valrectbytransition.animateRect(label="rect"){state->
when(state){
BoxState.Collapsed->Rect(0f,0f,100f,100f)
BoxState.Expanded->Rect(100f,100f,300f,300f)
}
}
valborderWidthbytransition.animateDp(label="borderWidth"){state->
when(state){
BoxState.Collapsed->1.dp
BoxState.Expanded->0.dp
}
}

Optimize animation performance

Animations in Compose can cause performance problems. This is due to the nature of what an animation is: moving or changing pixels on screen quickly, frame-by-frame to create the illusion of movement.

Consider the different phases of Compose: composition, layout and draw. If your animation changes the layout phase, it requires all affected composables to relayout and redraw. If your animation occurs in the draw phase, it is by default be more performant than if you were to run the animation in the layout phase, as it would have less work to do overall.

To ensure your app does as little as possible while animating, choose the lambda version of a Modifier where possible. This skips recomposition and performs the animation outside of the composition phase, otherwise use Modifier.graphicsLayer{ }, as this modifier always runs in the draw phase. For more information on this, see the deferring reads section in the performance documentation.

Change animation timing

Compose by default uses spring animations for most animations. Springs, or physics-based animations, feel more natural. They are also interruptible as they take into account the object's current velocity, instead of a fixed time. If you want to override the default, all the animation APIs demonstrated above have the ability to set an animationSpec to customize how an animation runs, whether you'd like it to execute over a certain duration or be more bouncy.

The following is a summary of the different animationSpec options:

  • spring: Physics-based animation, the default for all animations. You can change the stiffness or dampingRatio to achieve a different animation look and feel.
  • tween (short for between): Duration-based animation, animates between two values with an Easing function.
  • keyframes: Spec for specifying values at certain key points in an animation.
  • repeatable: Duration-based spec that runs a certain number of times, specified by RepeatMode.
  • infiniteRepeatable: Duration-based spec that runs forever.
  • snap: Instantly snaps to the end value without any animation.
Figure 16. No spec set vs Custom Spring spec set

Read the full documentation for more information about animationSpecs.

Additional resources

For more examples of fun animations in Compose, take a look at the following:

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2025年11月17日 UTC.