Animations in Jetpack Compose: From Basics to Shared Element Transitions
Compose Makes Good Animation the Path of Least Resistance
Animation in the old View system usually meant ObjectAnimator, XML animator resources, or reaching for a library. Compose bakes animation primitives directly into the state model, so "animate this value changing" is often a one-line change.
Level 1: animate a single value
val scale by animateFloatAsState(
targetValue = if (pressed) 1.2f else 1f,
label = "scale"
)
Box(modifier = Modifier.scale(scale))Whenever pressed changes, Compose automatically animates scale toward the new target — no manual animator setup, no cleanup to forget.
Level 2: animate visibility
AnimatedVisibility(
visible = showDetails,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
DetailsCard()
}This replaces a surprising amount of manual height/alpha animator code you'd otherwise write by hand in the View system.
Level 3: animate between different content
AnimatedContent(targetState = selectedTab, label = "tab") { tab ->
when (tab) {
Tab.Home -> HomeScreen()
Tab.Profile -> ProfileScreen()
}
}AnimatedContent handles the transition between two entirely different composables, with customizable enter/exit transforms per direction — useful for tab switches or wizard-style flows.
Level 4: shared element transitions
As of recent Compose versions, shared element transitions (an item in a list morphing into its detail screen) are supported natively via SharedTransitionLayout:
SharedTransitionLayout {
AnimatedContent(targetState = selected) { item ->
if (item == null) {
LazyColumn {
items(list) { i ->
ItemRow(
i,
Modifier.sharedElement(
rememberSharedContentState(key = i.id),
animatedVisibilityScope = this@AnimatedContent
)
)
}
}
} else {
DetailScreen(
item = selected,
modifier = Modifier.sharedElement(
rememberSharedContentState(key = selected.id),
animatedVisibilityScope = this@AnimatedContent
)
)
}
}
}This is the same visual effect as Flutter's Hero widget — an element visually "flies" between two screens — but expressed through Compose's state-driven model rather than an explicit widget wrapper.
Practical guidance
- Start with
animateXAsStatefor anything single-value — it covers 80% of real use cases. - Reach for
AnimatedVisibility/AnimatedContentonce you're animating whole composables appearing, disappearing, or swapping. - Save
SharedTransitionLayoutfor genuine navigation transitions where continuity between screens meaningfully improves the UX — it's more setup than it looks and easy to overuse. - Always give animations a
label— it makes debugging in Android Studio's animation inspector far less painful.