๐ŸŽจ Jetpack Compose

Jetpack Compose Animations for Beginners

Good animation explains change: where something came from, what just happened, what's loading. Compose makes most animations a one-line change. This guide starts with the simplest APIs and builds up, with guidance on motion that helps rather than distracts.

โœ๏ธ Yunok Tech ๐Ÿ“… Updated โฑ 3 min read ๐ŸŽฏ Beginner
On this page
  1. Which API should you use?
  2. 1. animate*AsState: animate a single value
  3. 2. AnimatedVisibility: enter and exit
  4. 3. AnimatedContent: swap content
  5. 4. animateContentSize: smooth expand and collapse
  6. 5. updateTransition: coordinate several values
  7. 6. Infinite animations: use sparingly
  8. Animation specs in one minute
  9. Motion and accessibility
  10. Where animation helps users
  11. Common mistakes
  12. Frequently asked questions
  13. Conclusion

In the View system, animating meant ObjectAnimators, listeners and careful cleanup. In Compose, animation follows the same idea as everything else: you change state, and the animation APIs smoothly move the UI from the old value to the new one.

Which API should you use?

Choosing a Compose animation API If a composable appears or disappears, use AnimatedVisibility. If content swaps based on state, use AnimatedContent or Crossfade. If size changes, use animateContentSize. If a single value such as color, size or alpha changes, use animate as state. If several values change together with one state, use updateTransition. If it repeats forever, use rememberInfiniteTransition. Appear / disappearAnimatedVisibility Content swapsAnimatedContent ยท Crossfade Size changesModifier.animateContentSize() One value changesanimate*AsState Many values, one stateupdateTransition Repeats foreverrememberInfiniteTransition Gesture-driven / fully customAnimatable (coroutine-based)
Start with the highest-level API that fits. Drop to Animatable only for gestures or custom choreography.

1. animate*AsState: animate a single value

Favourite buttonKotlin
@Composable
fun FavoriteButton(isFavorite: Boolean, onToggle: () -> Unit) {
    val scale by animateFloatAsState(
        targetValue = if (isFavorite) 1.2f else 1f,
        animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
        label = "favoriteScale",
    )
    val tint by animateColorAsState(
        targetValue = if (isFavorite) Color(0xFFE11D48) else MaterialTheme.colorScheme.onSurfaceVariant,
        label = "favoriteTint",
    )
    IconButton(onClick = onToggle) {
        Icon(
            imageVector = if (isFavorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
            contentDescription = if (isFavorite) "Remove from favourites" else "Add to favourites",
            tint = tint,
            modifier = Modifier.graphicsLayer { scaleX = scale; scaleY = scale },
        )
    }
}

Change the target and Compose animates to it, even if it changes again mid-animation. Using graphicsLayer applies the scale in the draw phase, which is cheaper than changing layout size.

2. AnimatedVisibility: enter and exit

Error bannerKotlin
AnimatedVisibility(
    visible = state.error != null,
    enter = slideInVertically { -it } + fadeIn(),
    exit = slideOutVertically { -it } + fadeOut(),
) {
    ErrorBanner(message = state.error.orEmpty(), onRetry = onRetry)
}

3. AnimatedContent: swap content

Animated counterKotlin
AnimatedContent(
    targetState = cartCount,
    transitionSpec = {
        if (targetState > initialState) slideInVertically { it } + fadeIn() togetherWith slideOutVertically { -it } + fadeOut()
        else slideInVertically { -it } + fadeIn() togetherWith slideOutVertically { it } + fadeOut()
    },
    label = "cartCount",
) { count -> Text("$count", style = MaterialTheme.typography.titleLarge) }

4. animateContentSize: smooth expand and collapse

Expandable textKotlin
var expanded by remember { mutableStateOf(false) }
Text(
    text = longDescription,
    maxLines = if (expanded) Int.MAX_VALUE else 3,
    overflow = TextOverflow.Ellipsis,
    modifier = Modifier.animateContentSize().clickable { expanded = !expanded },
)

5. updateTransition: coordinate several values

Selectable cardKotlin
val transition = updateTransition(targetState = selected, label = "cardSelection")
val elevation by transition.animateDp(label = "elevation") { if (it) 8.dp else 1.dp }
val borderWidth by transition.animateDp(label = "border") { if (it) 2.dp else 0.dp }
val bg by transition.animateColor(label = "bg") {
    if (it) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface
}

6. Infinite animations: use sparingly

Skeleton shimmer alphaKotlin
val infinite = rememberInfiniteTransition(label = "skeleton")
val alpha by infinite.animateFloat(
    initialValue = 0.4f, targetValue = 0.9f,
    animationSpec = infiniteRepeatable(tween(800), RepeatMode.Reverse),
    label = "skeletonAlpha",
)
Box(Modifier.fillMaxWidth().height(16.dp).graphicsLayer { this.alpha = alpha }.background(Color.LightGray))

Animation specs in one minute

  • spring(): physics-based and the default for most APIs. It handles interruptions naturally.
  • tween(durationMillis, easing): a fixed duration with an easing curve. Good for fades.
  • keyframes { }: specific values at specific times.

Motion and accessibility

Some users enable Remove animations in system settings. Compose's animation system respects the device's animator duration scale. Keep motion purposeful: short UI transitions (roughly 150โ€“300 ms) feel responsive. Never hide essential information inside an animation, and avoid large, continuous movement.

Where animation helps users

Feedback

A heart scaling on tap confirms the action happened.

Continuity

Expanding cards and list item animations show where content went.

Status

Skeleton placeholders and progress indicators set expectations while loading.

Common mistakes

  • Animating layout-affecting values (padding, size) every frame when a graphicsLayer transform would be cheaper.
  • Infinite animations left running off-screen or behind dialogs.
  • Omitting label, which makes Animation Preview and the inspector harder to use.
  • Over-animating: everything bouncing distracts and slows users down.

Frequently asked questions

Can I preview animations?

Yes. Android Studio's Animation Preview lets you inspect and scrub transitions, which is where labels help.

How do I animate between screens?

Navigation libraries provide enter and exit transitions, and shared element transitions are available in Compose for matching elements across screens.

Are Compose animations performant?

Yes, when they animate draw-phase properties (alpha, translation, scale via graphicsLayer) and avoid recomposing large trees every frame.

Conclusion

Compose animation is state-driven: change the state and let animate*AsState, AnimatedVisibility, AnimatedContent, animateContentSize, updateTransition or an infinite transition interpolate. Prefer high-level APIs, draw-phase transforms and purposeful, short motion.

Next step: make your app look polished and personal with Material 3 Theming and Dynamic Colors.

Official references

๐Ÿ“ฑ Apps built by Yunok Tech

Real Android apps on Google Play, free to download.

See all our apps โ†’

Ready to build real Android applications?

Jetpack Compose clicks once you build real screens with real state. Practise modern Android UI development on projects that go beyond toy examples.

Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.

Explore the Android course and take your next step as an Android developer.

Share: LinkedIn X / Twitter WhatsApp