animateAsState and Simple Animations
Why Animate?
Static UI feels abrupt. Animations guide the user's eye, provide feedback, and make transitions feel natural. Compose provides high-level animation APIs that handle interpolation, frame timing, and recomposition.
animateAsState
The simplest way to animate is animateAsState. It animates a value from its current state to a target state.
@Composable
fun ColorAnimation() {
var isBlue by remember { mutableStateOf(true) }
val color by animateColorAsState(
targetValue = if (isBlue) Color.Blue else Color.Red,
animationSpec = tween(durationMillis = 1000),
label = "color"
)
Box(
modifier = Modifier
.fillMaxSize()
.background(color)
.clickable { isBlue = !isBlue }
)
}
When isBlue changes, the color animates smoothly over 1000ms instead of jumping.
Available animateAsState Variants
// Float animation
val size by animateFloatAsState(
targetValue = if (expanded) 200f else 100f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "size"
)
// Dp animation
val padding by animateDpAsState(
targetValue = if (expanded) 32.dp else 8.dp,
animationSpec = tween(500),
label = "padding"
)
// Int animation
val count by animateIntAsState(
targetValue = targetCount,
animationSpec = tween(300),
label = "count"
)
AnimationSpec
AnimationSpec controls how the animation behaves:
// Linear: constant speed
linearAnimationSpec = tween(durationMillis = 300, easing = LinearEasing)
// EaseInOut: slow start, fast middle, slow end
easeInOutSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
// Spring: physics-based overshoot
springSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
// Tween with delay
delayedSpec = tween<Float>(durationMillis = 400, delayMillis = 200)
Animated Content
AnimatedContent crossfades between different composables based on state:
@Composable
fun AnimatedCounter(count: Int) {
AnimatedContent(
targetState = count,
transitionSpec = {
if (targetState > initialState) {
slideInVertically { -it } + fadeIn() togetherWith
slideOutVertically { it } + fadeOut()
} else {
slideInVertically { it } + fadeIn() togetherWith
slideOutVertically { -it } + fadeOut()
}
},
label = "counter"
) { targetCount ->
Text(
text = "$targetCount",
style = MaterialTheme.typography.displayLarge
)
}
}
AnimatedVisibility and Transitions
AnimatedVisibility
AnimatedVisibility animates a composable's appearance and disappearance:
@Composable
fun ExpandableCard(title: String, content: String) {
var expanded by remember { mutableStateOf(false) }
Card(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
AnimatedVisibility(
visible = expanded,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Text(
text = content,
modifier = Modifier.padding(top = 8.dp)
)
}
TextButton(onClick = { expanded = !expanded }) {
Text(if (expanded) "Show Less" else "Show More")
}
}
}
}
Enter and Exit Transitions
Combine multiple transitions:
// Enter transitions
fadeIn(tween(300))
slideInVertically(tween(300)) { it / 2 }
expandHorizontally(tween(400))
// Exit transitions
fadeOut(tween(300))
slideOutVertically(tween(300)) { it / 2 }
shrinkVertically(tween(400))
// Combine with +
val enterTransition = fadeIn(tween(300)) + slideInVertically(tween(300))
val exitTransition = fadeOut(tween(300)) + slideOutVertically(tween(300))
AnimatedVisibility(
visible = showItem,
enter = enterTransition,
exit = exitTransition
) {
ItemContent()
}
updateTransition
updateTransition synchronizes animations across multiple properties:
@Composable
fun BoxAnimation() {
var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "box")
val size by transition.animateDp(label = "size") { state ->
when (state) {
BoxState.Collapsed -> 100.dp
BoxState.Expanded -> 200.dp
}
}
val color by transition.animateColor(label = "color") { state ->
when (state) {
BoxState.Collapsed -> Color.Blue
BoxState.Expanded -> Color.Red
}
}
val cornerRadius by transition.animateDp(label = "corner") { state ->
when (state) {
BoxState.Collapsed -> 0.dp
BoxState.Expanded -> 24.dp
}
}
Box(
modifier = Modifier
.size(size)
.background(color, RoundedCornerShape(cornerRadius))
.clickable {
currentState = when (currentState) {
BoxState.Collapsed -> BoxState.Expanded
BoxState.Expanded -> BoxState.Collapsed
}
}
)
}
enum class BoxState { Collapsed, Expanded }
Infinite Animation
For looping animations like loading spinners:
@Composable
fun PulsingDot() {
val infiniteTransition = rememberInfiniteTransition(label = "pulse")
val scale by infiniteTransition.animateFloat(
initialValue = 0.8f,
targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = tween(600, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "scale"
)
Box(
modifier = Modifier
.size(16.dp)
.graphicsLayer { scaleX = scale; scaleY = scale }
.background(Color.Blue, CircleShape)
)
}
Quiz
1. What does animateAsState do?
2. Which animation spec provides physics-based overshoot behavior?
3. What is the difference between AnimatedVisibility and AnimatedContent?
4. How do you create a looping animation in Compose?
Flashcards
Question
What AnimationSpec types are available in Compose?
Click to reveal answer
Answer
tween (interpolated over time), spring (physics-based), keyframes (keyframe-based), snap (instant), infiniteRepeatable (looping).
Question
When should you use updateTransition vs animateAsState?
Click to reveal answer
Answer
updateTransition when multiple properties animate together in sync (e.g., size + color + shape). animateAsState for single independent values.
Question
What enter/exit transitions can AnimatedVisibility use?
Click to reveal answer
Answer
fadeIn/fadeOut, slideIn/OutVertically/Horizontally, expand/shrinkVertically/Horizontally. Combine multiple with + operator.
Question
What is RepeatMode in infinite animations?
Click to reveal answer
Answer
RepeatMode.Restart resets to initial value. RepeatMode.Reverse oscillates between initial and target values.
Revision Notes
Key Takeaways
- 1. animateAsState is the simplest way to animate a single value change
- 2. spring() provides natural physics-based motion without manual easing
- 3. AnimatedVisibility handles show/hide with enter and exit transitions
- 4. updateTransition synchronizes multiple animated properties for complex states
- 5. rememberInfiniteTransition creates looping animations for loading indicators
Interview Tips
- • Explain the difference between tween and spring and when to use each
- • Discuss how Compose animations avoid jank by running on the composition thread
- • Know how to combine enter/exit transitions for natural visibility changes
- • Be ready to describe how you would animate a complex state change with updateTransition
Cheat Sheet
Animation Cheat Sheet
Simple Animations:
- animateFloatAsState, animateColorAsState, animateDpAsState
- Pass targetValue and animationSpec
AnimationSpecs:
- tween(duration, easing) -- time-based
- spring(dampingRatio, stiffness) -- physics-based
- keyframes { at(0f); at(1f, 500ms) } -- keyframe-based
- snap() -- instant change
Visibility:
- AnimatedVisibility(visible, enter, exit)
- Enter: fadeIn, slideIn, expand
- Exit: fadeOut, slideOut, shrink
Multi-property:
- updateTransition(state) -- sync multiple animations
- transition.animateFloat/animateColor/animateDp
Looping:
- rememberInfiniteTransition()
- infiniteRepeatable(tween, RepeatMode.Reverse)
Content:
- AnimatedContent(targetState) -- crossfade between composables