Episode ini menghidupkan UI: animate*AsState untuk transisi nilai, AnimatedVisibility dan updateTransition, animasi berbasis fisika dengan spring, serta polish dari kombinasi animasi dan gesture.

UI yang baik memberi umpan balik visual: tombol membesar saat ditekan, panel membuka dengan mulus, elemen bergeser saat state berubah. Ini bukan hiasan — animasi membantu pengguna memahami apa yang terjadi.
Compose menjadikan animasi bagian dari bahasa state. Alih-alih menganimasikan view secara manual, kalian cukup mendeklarasikan nilai animasi dan membiarkan Compose menangani frame-nya.
Episode 12 ini meliputi animate*AsState, AnimatedVisibility, animasi berbasis fisika, dan polish dengan gesture.
Keluarga animate*AsState menganimasikan perpindahan satu nilai: animateDpAsState, animateFloatAsState, animateColorAsState, dan lainnya. Ketika target berubah, Compose menganimasikan transisinya:
@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 mengubah tinggi dari 80 dp ke 200 dp selama 300 milidetik. tween(durationMillis = 300) mengatur durasi; hasilnya kartu membuka dan menutup dengan mulus.
animateColorAsState menganimasikan perpindahan warna, cocok untuk status:
val warnaTombol by animateColorAsState(
targetValue = if (terkirim) Hijau else Merah
)
Button(onClick = { terkirim = true }) {
Text(if (terkirim) "Terkirim" else "Kirim")
}Warna berubah mulus mengikuti state terkirim. Gaya deklaratif ini membuat animasi mudah dibaca: target ditentukan state, sisanya urusan Compose.
AnimatedVisibility menganimasikan kemunculan dan penghilangan konten:
var tampilDetail by remember { mutableStateOf(false) }
AnimatedVisibility(
visible = tampilDetail,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
DetailKonten()
}enter = fadeIn() + expandVertically() menggabungkan efek kemunculan, dan exit menggabungkan efek penghilangan. Saat tampilDetail berubah, panel muncul dan hilang dengan halus.
updateTransition menganimasikan beberapa properti sekaligus berdasarkan satu 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 memegang satu target state dan menurunkan banyak animasi darinya. Transisi ini menjaga semua properti bergerak sinkron saat target berubah.
spring meniru fisika pegas: nilainya bergerak mengikuti stiffness dan damping, menghasilkan feel yang alami:
val offsetX by animateFloatAsState(
targetValue = if (geser) 320f else 0f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)DampingRatioMediumBouncy memberi efek memantul ringan, dan StiffnessLow membuat gerakan terasa lembut. Spring biasanya terasa lebih enak untuk interaksi pengguna daripada tween yang kaku.
Animatable memberi kontrol penuh — misalnya untuk mengikuti gesture drag:
val geser = remember { Animatable(0f) }
Modifier.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
geser.snapTo(geser.value + dragAmount.x)
}
}geser.snapTo(...) menempatkan nilai langsung mengikuti jari, dan saat dilepas kalian bisa geser.animateTo(0f) untuk kembali ke posisi awal. Kombinasi gesture dan Animatable adalah dasar komponen swipe-to-dismiss. Untuk mengecek statistik frame selama animasi berjalan, gunakan adb shell dumpsys gfxinfo di terminal.
Saat konten berganti total, Crossfade memudar antar dua konten:
Crossfade(targetState = halamanAktif, label = "halaman") { halaman ->
when (halaman) {
"login" -> LoginScreen()
"beranda" -> BerandaScreen()
}
}Crossfade memudarkan keluar halaman lama dan memudarkan masuk halaman baru. Prinsipnya konsisten: nyatakan target dari state, dan Compose mengisi transisinya.
Episode 12 menghidupkan UI: animate*AsState untuk transisi nilai, AnimatedVisibility dengan enter dan exit, updateTransition untuk banyak properti sinkron, spring untuk feel yang alami, Animatable untuk kontrol gesture, dan Crossfade untuk pergantian konten.
Inti yang harus dibawa pulang:
Di episode 13 selanjutnya kita akan membahas accessibility dan internationalization — Semantics API, content descriptions, fokus dan screen reader, lokalisasi, dukungan RTL, serta format yang sadar locale.