
“Jetpack Compose makes animations not only possible, but delightful — using state-driven, declarative APIs that feel natural and clean.”
Understanding animate*AsState in Jetpack Compose
Before we dive into specific animation examples, it’s worth getting familiar with the powerful and beginner-friendly animate*AsState APIs — the foundation of most state-based animations in Jetpack Compose.
What is animate*AsState?
Jetpack Compose’s animate*AsState functions help you animate values when state changes. Instead of instantly jumping from one value to another, Compose animates the change smoothly.
There are several built-in versions:
animateDpAsState()– for sizes and dimensionsanimateFloatAsState()– for rotation, alpha, progress, etc.animateColorAsState()– for animating color changesanimateIntAsState(),animateOffsetAsState(), etc.
They all share the same structure.
Why it’s great:
- Simple and declarative: Just swap state, and the animation happens.
- Reusable: Works inside any layout or modifier (like
size(),rotate(), orpadding()). - Powerful: Supports different animation specs (
tween,spring,keyframes, etc.)
Now that we understand the magic behind animate*AsState, let’s explore how to use it in real-world UI animations like size transitions, visibility toggles, and interactive rotations. 👇
In this article, we’ll explore how to create smooth size transitions using one of the simplest but most powerful tools in Jetpack Compose: animateDpAsState,animateFloatAsState and AnimatedVisibility.
We’ll walk through the example line by line, explain the key concepts like remember, MutableState, and tween, and show how Compose handles animation with minimal effort.
1. Growing and Shrinking a Box
@Composable
fun SizeAnimationExample() {
var isGrow by remember { mutableStateOf(false) }
val boxSize by animateDpAsState(
targetValue = if (isGrow) 200.dp else 100.dp,
animationSpec = tween(durationMillis = 1000),
label = ""
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = { isGrow = !isGrow }) {
Text(if (isGrow) "Shrink Box" else "Grow Box")
}
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier = Modifier
.size(boxSize)
.background(Color.Red)
)
}
}

Think of it like a switch
This animation works like a light switch:
- When
isGrowis false, the box is small (100.dp). - When you click the button and flip
isGrowto true, the box smoothly grows to 200.dp.
So what’s actually happening under the hood? Let’s break it down line by line
Line 1: var isGrow by remember { mutableStateOf(false) }
var isGrow: This is a mutable Boolean state.remember: Ensures that the value is retained across recompositions.mutableStateOf(false): Creates observable state initialized asfalse.
When
isGrow changes, Compose automatically triggers a recomposition — redrawing any Composables that depend on it.
Line 2: val boxSize by animateDpAsState(...)
This line is where the animation happens.
animateDpAsState: Animates aDpvalue over time when its target changes.targetValue: IfisGrowis true → animate to200.dp, otherwise →100.dp.tween(durationMillis = 1000): Defines a 1-second smooth animation.boxSizebecomes a smoothly animated value between those two endpoints.
As
isGrow changes,boxSize changes gradually — and because it’s used insideModifier.size(), the box resizes smoothly.
How the UI responds
Let’s walk through the flow:
- On launch,
isGrow = false→ box starts at100.dp. - You click the button →
isGrow = true. animateDpAsStateanimates from100.dpto200.dp.- The
Boxmodifier updates with the new animated size in real time. - Click again → animation reverses.
The result is a polished, native-feeling transition — with no manual animation code, handlers, or frame calculations.
Fading Elements In and Out with AnimatedVisibility
Jetpack Compose offers a very expressive way to show or hide content smoothly using the AnimatedVisibility composable.
What is AnimatedVisibility?
Think of it like a magic container:
When a Boolean value (e.g., isVisible) changes, it handles both the appearance and disappearance of its content with animation.
2. Fade a Box In and Out
@Composable
fun VisibilityAnimationTest() {
var isVisible by remember { mutableStateOf(true) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "Hide" else "Show")
}
Spacer(modifier = Modifier.height(16.dp))
AnimatedVisibility(
visible = isVisible,
enter = fadeIn(animationSpec = tween(durationMillis = 1000)),
exit = fadeOut(animationSpec = tween(durationMillis = 1000))
) {
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Blue)
)
}
}
}

How It Works
1. visible = isVisible
- This controls whether the content should be visible.
isVisibleis aMutableStatevalue that determines the visibility.- When this value changes, the UI recomposes and the proper animation is triggered.
2. enter = fadeIn(...)
- Defines how the content should appear.
fadeIn()is a built-in transition that starts at full transparency and fades to visible.tween(durationMillis = 1000)makes the fade-in last for 1 second.
When isVisible becomes true, the content fades in.
3. exit = fadeOut(...)
- Defines how the content should disappear.
fadeOut()gradually fades the content out until it’s fully transparent.- It also uses
tween(durationMillis = 1000)to last 1 second.
When isVisible becomes false, the content fades out.
TL;DR — In Simple Terms
AnimatedVisibility= “Show/Hide with animation”isVisible= “The switch that controls visibility”fadeIn()= “Smooth entry”, andfadeOut()= “Smooth exit”tween(1000)= “Take 1 second to animate”
Full Lifecycle
- Initial state:
isVisible = true→ content is shown. - You click the button →
isVisible = false. AnimatedVisibilitydetects the change:- Triggers the exit animation (
fadeOut) - Fades the content out over 1 second
- Click again →
isVisible = true: - Triggers enter animation (
fadeIn) - Content fades back in
All of this happens automatically — no manual animation logic required.
Pro Tip
You’re not limited to just fading. You can combine or replace transitions with:
slideInVertically(),slideOutHorizontally()expandVertically(),shrinkOut()scaleIn(),scaleOut()
3. Animating Rotation with animateFloatAsState
Sometimes you want to add a little motion to your interactions like rolling a dice or refreshing content. Here’s how you can do that with animateFloatAsState:
Setup
var isRolling by remember { mutableStateOf(false) }
val rotationAngle by animateFloatAsState(
targetValue = if (isRolling) 360f else 0f,
animationSpec = tween(durationMillis = 500),
finishedListener = {
isRolling = false
}
)
Usage in a Button
IconButton(
onClick = {
if (!isRolling) {
isRolling = true
viewModel.fetchZenQuote()
}
},
modifier = Modifier.rotate(rotationAngle),
) {
Icon(
imageVector = Icons.Default.Casino,
contentDescription = "New quote",
tint = Color.White,
)
}

Job Offers
How It Works
isRollingstarts asfalse.- When the user taps the button, it flips
isRollingtotrue. animateFloatAsStatedetects the change and animatesrotationAnglefrom0f→360f.- Once the spin completes,
finishedListenerresetsisRollingtofalse.
This creates a clean, polished 360° spin effect every time the button is pressed. It’s perfect for dice rolls, card shuffles, or random content refreshes.
Why This Works So Well in Compose
Jetpack Compose animations are:
- Declarative — Just describe the end state.
- State-driven — Powered by
rememberandMutableState. - Composable — Plug into any layout or modifier easily.
You don’t manage time or frames and Compose handles that internally. All you do is update state, and the animation system figures out how to transition the UI.
Follow me for more Android and Tech content!
Let’s connect on Linkedin!
This article was previously published on proandroiddev.com.



