Halaman ini menjelaskan cara membuat animasi berbasis nilai di Jetpack Compose, dengan berfokus pada API yang menganimasikan nilai berdasarkan status saat ini dan targetnya.
Menganimasikan satu nilai dengan animate*AsState
Fungsi animate*AsState adalah Animation API yang sederhana di
Compose untuk menganimasikan satu nilai. Anda hanya memberikan nilai target
(atau nilai akhir), dan API akan memulai animasi dari nilai saat ini ke
nilai yang ditentukan.
Contoh berikut menganimasikan alfa menggunakan API ini. Dengan menggabungkan nilai target
di animateFloatAsState, nilai alfa sekarang menjadi nilai animasi
di antara nilai yang disediakan (1f atau 0.5f dalam kasus ini).
var enabled by remember { mutableStateOf(true) } val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer { alpha = animatedAlpha } .background(Color.Red) )
Anda tidak perlu membuat instance dari kelas animasi atau menangani
gangguan. Di balik layar, objek animasi (yaitu, instance Animatable) akan dibuat dan diingat di situs panggilan, dengan nilai target pertama sebagai nilai awal. Sejak saat itu, setiap kali Anda memberikan nilai target yang berbeda untuk composable ini, animasi akan otomatis dimulai terhadap nilai tersebut. Jika saat ini sudah ada animasi, animasi dimulai dari nilai saat ini (dan kecepatannya) dan bergerak menuju nilai target. Selama
animasi, composable ini dapat dikomposisi ulang dan menampilkan nilai animasi yang diupdate setiap frame.
Secara default, Compose menyediakan fungsi animate*AsState untuk Float, Color,
Dp, Size, Offset, Rect, Int, IntOffset, dan IntSize. Anda dapat menambahkan
dukungan untuk jenis data lainnya dengan memberikan TwoWayConverter ke
animateValueAsState yang menggunakan jenis umum.
Anda dapat menyesuaikan spesifikasi animasi dengan menyediakan
AnimationSpec. Lihat AnimationSpec untuk mengetahui informasi selengkapnya.
Menganimasikan beberapa properti secara bersamaan dengan transisi
Transition mengelola satu atau beberapa animasi sebagai turunannya dan menjalankannya
secara bersamaan di beberapa status.
Status dapat berupa jenis data apa pun. Dalam banyak kasus, Anda dapat menggunakan jenis enum kustom untuk memverifikasi keamanan jenis, seperti dalam contoh berikut:
enum class BoxState { Collapsed, Expanded }
updateTransition membuat dan mengingat instance Transition dan
memperbarui statusnya.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Anda kemudian dapat menggunakan salah satu dari fungsi ekstensi animate* untuk menentukan animasi turunan dalam transisi ini. Menentukan nilai target untuk setiap status.
Fungsi animate* ini menampilkan nilai animasi yang diperbarui setiap frame selama animasi saat status transisi diperbarui dengan updateTransition.
val rect by transition.animateRect(label = "rectangle") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "border width") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Secara opsional, Anda dapat meneruskan parameter transitionSpec untuk menentukan AnimationSpec yang berbeda untuk setiap kombinasi perubahan status transisi. Lihat
AnimationSpec untuk mengetahui informasi selengkapnya.
val color by transition.animateColor( transitionSpec = { when { BoxState.Expanded isTransitioningTo BoxState.Collapsed -> spring(stiffness = 50f) else -> tween(durationMillis = 500) } }, label = "color" ) { state -> when (state) { BoxState.Collapsed -> MaterialTheme.colorScheme.primary BoxState.Expanded -> MaterialTheme.colorScheme.background } }
Setelah transisi mencapai status target, Transition.currentState akan sama dengan Transition.targetState. Anda dapat menggunakannya sebagai sinyal apakah
transisi telah selesai.
Terkadang, Anda mungkin ingin memiliki status awal yang berbeda dengan status target pertama. Anda dapat menggunakan updateTransition dengan MutableTransitionState untuk
mencapainya. Misalnya, memungkinkan Anda memulai animasi segera setelah kode
memasuki komposisi.
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // ……
Untuk transisi lebih kompleks yang melibatkan beberapa fungsi composable, Anda dapat
menggunakan createChildTransition untuk membuat transisi turunan. Teknik ini berguna untuk memisahkan masalah di antara beberapa subkomponen dalam composable yang kompleks. Transisi induk memperhatikan semua nilai animasi di
transisi turunan.
enum class DialerState { DialerMinimized, NumberPad } @Composable fun DialerButton(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun NumberPad(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun Dialer(dialerState: DialerState) { val transition = updateTransition(dialerState, label = "dialer state") Box { // Creates separate child transitions of Boolean type for NumberPad // and DialerButton for any content animation between visible and // not visible NumberPad( transition.createChildTransition { it == DialerState.NumberPad } ) DialerButton( transition.createChildTransition { it == DialerState.DialerMinimized } ) } }
Menggunakan transisi dengan AnimatedVisibility dan AnimatedContent
AnimatedVisibility dan AnimatedContent tersedia sebagai fungsi ekstensi dari Transition. targetState untuk Transition.AnimatedVisibility
dan Transition.AnimatedContent berasal dari Transition, dan memicu
animasi masuk, keluar, dan sizeTransform sesuai kebutuhan saat targetState
Transition berubah. Fungsi ekstensi ini memungkinkan Anda mengangkat semua animasi masuk, keluar,
dan sizeTransform yang akan bersifat internal ke
AnimatedVisibility/AnimatedContent ke dalam Transition. Dengan fungsi ekstensi ini, Anda dapat mengamati perubahan status AnimatedVisibility/AnimatedContent dari luar. Sebagai ganti parameter visible boolean, versi AnimatedVisibility ini menggunakan lambda yang mengonversi status target transisi induk ke dalam boolean.
Lihat AnimatedVisibility dan AnimatedContent untuk mengetahui detailnya.
var selected by remember { mutableStateOf(false) } // Animates changes when `selected` is changed. val transition = updateTransition(selected, label = "selected state") val borderColor by transition.animateColor(label = "border color") { isSelected -> if (isSelected) Color.Magenta else Color.White } val elevation by transition.animateDp(label = "elevation") { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( onClick = { selected = !selected }, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, borderColor), shadowElevation = elevation ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text(text = "Hello, world!") // AnimatedVisibility as a part of the transition. transition.AnimatedVisibility( visible = { targetSelected -> targetSelected }, enter = expandVertically(), exit = shrinkVertically() ) { Text(text = "It is fine today.") } // AnimatedContent as a part of the transition. transition.AnimatedContent { targetState -> if (targetState) { Text(text = "Selected") } else { Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone") } } } }
Melakukan enkapsulasi transisi dan membuatnya dapat digunakan kembali
Untuk kasus penggunaan yang sederhana, menentukan animasi transisi dalam composable yang sama dengan UI adalah opsi yang valid. Namun, saat Anda mengerjakan komponen yang rumit dengan sejumlah nilai animasi, sebaiknya pisahkan penerapan animasi dari composable UI.
Anda dapat melakukannya dengan membuat class yang menyimpan semua nilai animasi dan fungsi update yang menampilkan instance class tersebut. Anda dapat mengekstrak
penerapan transisi ke fungsi terpisah yang baru. Pola ini berguna
saat Anda perlu memusatkan logika animasi atau membuat animasi yang rumit
dapat digunakan kembali.
enum class BoxState { Collapsed, Expanded } @Composable fun AnimatingBox(boxState: BoxState) { val transitionData = updateTransitionData(boxState) // UI tree Box( modifier = Modifier .background(transitionData.color) .size(transitionData.size) ) } // Holds the animation values. private class TransitionData( color: State<Color>, size: State<Dp> ) { val color by color val size by size } // Create a Transition and return its animation values. @Composable private fun updateTransitionData(boxState: BoxState): TransitionData { val transition = updateTransition(boxState, label = "box state") val color = transition.animateColor(label = "color") { state -> when (state) { BoxState.Collapsed -> Color.Gray BoxState.Expanded -> Color.Red } } val size = transition.animateDp(label = "size") { state -> when (state) { BoxState.Collapsed -> 64.dp BoxState.Expanded -> 128.dp } } return remember(transition) { TransitionData(color, size) } }
Membuat animasi berulang tanpa batas dengan rememberInfiniteTransition
InfiniteTransition memiliki satu atau beberapa animasi turunan seperti Transition,
tetapi animasi akan mulai berjalan segera setelah dimasukkan ke dalam komposisi dan tidak
berhenti kecuali dihapus. Anda dapat membuat instance InfiniteTransition
dengan rememberInfiniteTransition, dan menambahkan animasi turunan dengan animateColor,
animateFloat, atau animateValue. Anda juga perlu menentukan
infiniteRepeatable untuk menentukan spesifikasi animasi.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Box( Modifier .fillMaxSize() .background(color) )
Animation API tingkat rendah
Semua Animation API tingkat tinggi yang disebutkan di bagian sebelumnya dibuat berdasarkan Animation API tingkat rendah.
Fungsi animate*AsState adalah API sederhana yang merender perubahan nilai instan sebagai nilai animasi. Fungsi ini didukung oleh
Animatable, API berbasis coroutine untuk menganimasikan nilai tunggal.
updateTransition membuat objek transisi yang dapat mengelola beberapa nilai animasi dan menjalankannya saat status berubah. rememberInfiniteTransition
serupa, tetapi membuat transisi tidak terbatas yang dapat mengelola beberapa
animasi yang terus berjalan tanpa batas. Semua API ini merupakan composable kecuali
Animatable, yang berarti Anda dapat membuat animasi ini di luar
komposisi.
Semua API ini didasarkan pada Animation API yang lebih mendasar. Meskipun sebagian besar
aplikasi tidak akan berinteraksi langsung dengan Animation, Anda dapat mengakses beberapa
kemampuan penyesuaiannya melalui API tingkat yang lebih tinggi. Lihat Menyesuaikan
animasi untuk mengetahui informasi selengkapnya tentang AnimationVector dan AnimationSpec.
Animatable: Animasi nilai tunggal berbasis coroutine
Animatable adalah holder nilai yang dapat menganimasikan nilai saat diubah
menggunakan animateTo. Ini adalah API yang mendukung implementasi
animate*AsState. Hal ini memastikan kelanjutan yang konsisten dan saling eksklusif,
yang berarti perubahan nilai selalu berkelanjutan dan Compose membatalkan
animasi yang sedang berlangsung.
Banyak fitur Animatable, termasuk animateTo, merupakan fungsi penangguhan.
Artinya, Anda harus menggabungkannya dalam cakupan coroutine yang sesuai. Misalnya,
Anda dapat menggunakan composable LaunchedEffect untuk membuat cakupan hanya selama
durasi nilai kunci yang ditentukan.
// Start out gray and animate to green/red based on `ok` val color = remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box( Modifier .fillMaxSize() .background(color.value) )
Pada contoh sebelumnya, Anda membuat dan mengingat instance Animatable
dengan nilai awal Color.Gray. Bergantung pada nilai flag boolean ok, warna animasi ke Color.Green atau Color.Red. Setiap
perubahan berikutnya pada nilai boolean akan memulai animasi ke warna lainnya.
Jika animasi sedang berlangsung saat nilai berubah, Compose akan membatalkan
animasi, dan animasi baru akan dimulai dari nilai snapshot saat ini dengan
kecepatan saat ini.
Animatable API ini adalah implementasi dasar untuk animate*AsState
yang disebutkan di bagian sebelumnya. Menggunakan Animatable secara langsung menawarkan
kontrol yang lebih mendetail dalam beberapa cara:
- Pertama,
Animatabledapat memiliki nilai awal yang berbeda dari nilai target pertamanya. Misalnya, contoh kode sebelumnya menampilkan kotak abu-abu terlebih dahulu, yang segera beranimasi menjadi hijau atau merah. - Kedua,
Animatablememberikan lebih banyak operasi pada nilai konten, khususnyasnapTodananimateDecay.snapTosegera menyetel nilai saat ini ke nilai target. Hal ini berguna saat animasi bukan satu-satunya sumber kebenaran dan harus disinkronkan dengan status lain, seperti peristiwa sentuh.animateDecaymemulai animasi yang melambat dari kecepatan yang ditentukan. Ini berguna untuk menerapkan perilaku fling.
Lihat Gestur dan animasi untuk informasi selengkapnya.
Secara default, Animatable mendukung Float dan Color, tetapi Anda dapat menggunakan jenis data apa pun dengan menyediakan TwoWayConverter. Lihat AnimationVector untuk mengetahui informasi selengkapnya.
Anda dapat menyesuaikan spesifikasi animasi dengan menyediakan AnimationSpec.
Lihat AnimationSpec untuk mengetahui informasi selengkapnya.
Animation: Animasi yang dikontrol secara manual
Animation adalah Animation API tingkat terendah yang tersedia. Banyak animasi yang telah kita lihat sejauh ini dibuat di atas Animation. Ada dua subjenis Animation: TargetBasedAnimation dan DecayAnimation.
Gunakan Animation hanya untuk mengontrol waktu animasi secara manual. Animation bersifat stateless, dan tidak memiliki konsep siklus proses apa pun. Hal ini berfungsi sebagai
mesin hitung animasi untuk API dengan tingkat yang lebih tinggi.
TargetBasedAnimation
API lain mencakup sebagian besar kasus penggunaan, tetapi penggunaan TargetBasedAnimation secara langsung memungkinkan
Anda mengontrol waktu pemutaran animasi. Pada contoh berikut, Anda mengontrol
waktu pemutaran TargetAnimation secara manual berdasarkan waktu render frame yang disediakan oleh
withFrameNanos.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
Tidak seperti TargetBasedAnimation, DecayAnimation tidak memerlukan
targetValue yang disediakan. Sebaliknya, elemen ini menghitung targetValue berdasarkan
kondisi awal, yang ditetapkan oleh initialVelocity dan initialValue serta
DecayAnimationSpec yang disediakan.
Animasi decay sering digunakan setelah gestur ayunkan jari untuk memperlambat elemen hingga
berhenti. Kecepatan animasi dimulai pada nilai yang ditetapkan oleh initialVelocityVector
dan melambat seiring waktu.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Menyesuaikan animasi
- Animasi di Compose
- Pengubah dan composable animasi