Learn Jetpack Compose - Animations & Motion
Episode 12 of 23

Learn Jetpack Compose - Animations & Motion

This episode brings the UI to life: animate*AsState for value transitions, AnimatedVisibility and updateTransition, physics-based animations with spring, and polish from combining animations and gestures.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

Good UI gives visual feedback: buttons grow when pressed, panels open smoothly, elements shift when state changes. This isn't decoration — animation helps users understand what is happening.

Compose makes animation part of the language of state. Instead of manually animating views, you simply declare an animated value and let Compose handle the frames.

Episode 12 covers animate*AsState, AnimatedVisibility, physics-based animations, and polish with gestures.

Basic Animations with animate*AsState

Animating Values

The animate*AsState family animates the transition of a single value: animateDpAsState, animateFloatAsState, animateColorAsState, and others. When the target changes, Compose animates the transition:

KotlinanimateDpAsState
@Composable
fun KartuDiperluas(perluas: Boolean) {
    val tinggi by animateDpAsState(
        targetValue = if (perluas) 200.dp else 80.dp,
        animationSpec = tween(durationMillis = 300)
    )
 
    Card(Modifier.fillMaxWidth().height(tinggi)) {
        Text("Tinggi: $tinggi", Modifier.padding(16.dp))
    }
}

animateDpAsState changes tinggi from 80 dp to 200 dp over 300 milliseconds. tween(durationMillis = 300) sets the duration; the result is a card that opens and closes smoothly.

Color and Size Animations

animateColorAsState animates color transitions, suitable for status:

KotlinanimateColorAsState
val warnaTombol by animateColorAsState(
    targetValue = if (terkirim) Hijau else Merah
)
Button(onClick = { terkirim = true }) {
    Text(if (terkirim) "Terkirim" else "Kirim")
}

The color changes smoothly following the terkirim state. This declarative style makes animations easy to read: the state defines the target, and Compose takes care of the rest.

AnimatedVisibility and updateTransition

Showing and Hiding

AnimatedVisibility animates the appearance and disappearance of content:

KotlinAnimatedVisibility
var tampilDetail by remember { mutableStateOf(false) }
 
AnimatedVisibility(
    visible = tampilDetail,
    enter = fadeIn() + expandVertically(),
    exit = fadeOut() + shrinkVertically()
) {
    DetailKonten()
}

enter = fadeIn() + expandVertically() combines entrance effects, and exit combines exit effects. When tampilDetail changes, the panel appears and disappears smoothly.

updateTransition for Multiple Properties

updateTransition animates several properties at once based on a single state:

KotlinupdateTransition
val transition = updateTransition(targetState = tampil, label = "panel")
val alpha by transition.animateFloat(
    transitionSpec = { tween(300) }
) { tampil -> if (tampil) 1f else 0f }
val ukuran by transition.animateDp(
    transitionSpec = { spring(stiffness = Spring.StiffnessLow) }
) { tampil -> if (tampil) 96.dp else 48.dp }

updateTransition holds one target state and derives many animations from it. This transition keeps all properties moving in sync when the target changes.

Physics-based Animations

Spring: Natural Feel

spring mimics a physical spring: the value moves following stiffness and damping, producing a natural feel:

KotlinSpring animation
val offsetX by animateFloatAsState(
    targetValue = if (geser) 320f else 0f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioMediumBouncy,
        stiffness = Spring.StiffnessLow
    )
)

DampingRatioMediumBouncy gives a light bounce effect, and StiffnessLow makes the movement feel soft. Springs usually feel better for user interactions than rigid tweens.

Animatable for Full Control

Animatable gives full control — for example to follow a drag gesture:

KotlinAnimatable
val geser = remember { Animatable(0f) }
 
Modifier.pointerInput(Unit) {
    detectDragGestures { change, dragAmount ->
        change.consume()
        geser.snapTo(geser.value + dragAmount.x)
    }
}

geser.snapTo(...) places the value directly under your finger, and when released you can geser.animateTo(0f) to return to the start position. Combining gestures and Animatable is the foundation of swipe-to-dismiss components. To check frame statistics while an animation runs, use adb shell dumpsys gfxinfo in the terminal.

Polish with Smooth Transitions

Crossfade and AnimatedContent

When content changes entirely, Crossfade fades between two contents:

KotlinCrossfade
Crossfade(targetState = halamanAktif, label = "halaman") { halaman ->
    when (halaman) {
        "login" -> LoginScreen()
        "beranda" -> BerandaScreen()
    }
}

Crossfade fades out the old page and fades in the new one. The principle is consistent: declare the target from state, and Compose fills in the transition.

Closing

Episode 12 brought the UI to life: animate*AsState for value transitions, AnimatedVisibility with enter and exit, updateTransition for multiple synchronized properties, spring for a natural feel, Animatable for gesture control, and Crossfade for content changes.

Key takeaways:

  • animate*AsState animates a single value from a target state.
  • AnimatedVisibility combines enter and exit effects.
  • updateTransition synchronizes many animations within one state.
  • spring gives natural movement with damping and stiffness.
  • Animatable gives full control for gesture interactions.
  • Crossfade fades complete content changes.

In episode 13 we will discuss accessibility and internationalization — the Semantics API, content descriptions, focus and screen readers, localization, RTL support, and locale-aware formatting.

Learn Jetpack Compose - Animations & Motion | Learn Jetpack Compose