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.

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.
The animate*AsState family animates the transition of a single value: animateDpAsState, animateFloatAsState, animateColorAsState, and others. When the target changes, Compose animates the transition:
@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.
animateColorAsState animates color transitions, suitable for status:
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 animates the appearance and disappearance of content:
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 animates several properties at once based on a single state:
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.
spring mimics a physical spring: the value moves following stiffness and damping, producing a natural feel:
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 gives full control — for example to follow a drag gesture:
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.
When content changes entirely, Crossfade fades between two contents:
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.
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:
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.