What you'll learn
Motion is what makes an app feel alive and responsive — a button that pulses, a panel that slides, colors that ease instead of snapping. Compose's animation APIs are unusually elegant: because the UI is already driven by state, animating is often as simple as swapping a value for an animated value.
By the end you'll be able to:
- Animate single values with
animate*AsState - Animate appearance with
AnimatedVisibilityandCrossfade - Coordinate several values with
updateTransition - Tune motion with
springandtweenspecs
animate*AsState
The workhorse. There's an animate*AsState for each common type — animateDpAsState, animateColorAsState, animateFloatAsState. You give it a targetValue; whenever that target changes, it animates from the current value to the new one automatically, and you just read the animated value:
@Composable
fun LikeButton() {
var liked by remember { mutableStateOf(false) }
// The color animates smoothly whenever 'liked' flips — no manual tweening.
val color by animateColorAsState(
targetValue = if (liked) Color.Red else Color.Gray,
label = "likeColor",
)
Icon(
Icons.Default.Favorite,
contentDescription = "Like",
tint = color,
modifier = Modifier.clickable { liked = !liked },
)
}Key idea
AnimatedVisibility & Crossfade
To animate a composable entering or leaving, wrap it in AnimatedVisibility — it fades and expands in, then shrinks and fades out, all configurable. To swap between two composables with a fade, use Crossfade:
AnimatedVisibility(visible = expanded) {
Text("Extra details revealed with a smooth expand/fade.")
}
// Swap between two composables with a fade:
Crossfade(targetState = screen, label = "screen") { current ->
when (current) {
Screen.Home -> HomeContent()
Screen.Detail -> DetailContent()
}
}Multi-value transitions
When several properties should animate together off one state change — a card that grows its elevation and padding when selected — use updateTransition. It drives multiple animations from a single source state, keeping them perfectly in sync:
val transition = updateTransition(targetState = selected, label = "card")
val elevation by transition.animateDp(label = "elevation") { if (it) 12.dp else 2.dp }
val padding by transition.animateDp(label = "padding") { if (it) 24.dp else 16.dp }
// elevation and padding animate together, in sync.Tuning with animationSpec
Every animation takes an animationSpec that shapes how it moves. A spring gives natural, physics-based motion (optionally bouncy) and is the Compose default; a tween runs for a fixed duration with an easing curve. Choose spring for most UI — it feels organic and adapts if the target changes mid-flight:
val offset by animateDpAsState(
targetValue = target,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
label = "offset",
)
// Or a fixed duration: tween(durationMillis = 300, easing = FastOutSlowInEasing)Tip
Recap & quick check
Key takeaways
- animate*AsState animates a single value to a new target automatically — you just read the animated value.
- AnimatedVisibility animates composables entering/leaving; Crossfade fades between two composables.
- updateTransition coordinates several animations from one source state, keeping them in sync.
- animationSpec shapes the motion: spring (natural, physics-based, the default) or tween (fixed duration + easing).
- Animate with purpose and keep it short; respect reduce-motion accessibility settings.
Quick check
1. How do you smoothly animate a color when a boolean flips?
2. Which composable animates content appearing and disappearing?
3. You need elevation and padding to animate together from one state. Use…
4. What does animationSpec control?
5. Which spec gives natural, physics-based motion and is the Compose default?
Your UI moves beautifully. Next we go beyond the built-in layouts — building custom layouts and drawing on the Canvas.