Compose memiliki banyak mekanisme animasi bawaan dan bisa sangat melelahkan untuk tahu mana yang harus dipilih. Di bawah ini adalah daftar kasus penggunaan animasi yang umum. Sebagai informasi lebih mendetail tentang rangkaian lengkap opsi API yang berbeda baca dokumentasi Animasi Compose lengkap.
Menganimasikan properti composable umum
Compose menyediakan API yang mudah digunakan yang memungkinkan Anda menyelesaikan berbagai masalah kasus penggunaan animasi. Bagian ini menunjukkan cara menganimasikan properti dari sebuah composable.
Menganimasikan muncul / hilang
![Composable hijau menampilkan dan menyembunyikan dirinya sendiri](https://developer.android.com/static/develop/ui/compose/images/animations/animated_visibility_column.gif?authuser=9&hl=id)
Gunakan AnimatedVisibility
untuk menyembunyikan atau menampilkan Composable. Anak-anak di dalam
AnimatedVisibility
dapat menggunakan Modifier.animateEnterExit()
untuk enter-nya sendiri
atau keluar dari transisi.
var visible by remember { mutableStateOf(true) } // Animated visibility will eventually remove the item from the composition once the animation has finished. AnimatedVisibility(visible) { // your composable here // ... }
Parameter masuk dan keluar AnimatedVisibility
memungkinkan Anda mengonfigurasi cara
composable berperilaku saat muncul dan menghilang. Baca lengkap
dokumentasi untuk informasi selengkapnya.
Opsi lain untuk menganimasikan visibilitas composable adalah dengan menganimasikan
alfa dari waktu ke waktu menggunakan animateFloatAsState
:
var visible by remember { mutableStateOf(true) } val animatedAlpha by animateFloatAsState( targetValue = if (visible) 1.0f else 0f, label = "alpha" ) Box( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = animatedAlpha } .clip(RoundedCornerShape(8.dp)) .background(colorGreen) .align(Alignment.TopCenter) ) { }
Namun, mengubah alfa disertai persyaratan bahwa composable tetap ada
dalam komposisi dan terus menempati ruang tempatnya berada. Ini
dapat menyebabkan {i>screen reader<i} dan mekanisme
aksesibilitas lain masih mempertimbangkan
item yang ada di layar. Di sisi lain, AnimatedVisibility
pada akhirnya menghapus
item dari komposisi.
![Menganimasikan alfa composable](https://developer.android.com/static/develop/ui/compose/images/animations/animated_visibility_alpha.gif?authuser=9&hl=id)
Animasikan warna latar belakang
![Composable dengan warna latar belakang yang berubah seiring waktu sebagai animasi, dengan warna yang memudar satu sama lain.](https://developer.android.com/static/develop/ui/compose/images/animations/animated_forever.gif?authuser=9&hl=id)
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Opsi ini berperforma lebih tinggi daripada menggunakan Modifier.background()
.
Modifier.background()
dapat diterima untuk setelan warna satu kali, tetapi jika
menganimasikan warna dari waktu ke waktu, ini dapat menyebabkan lebih banyak rekomposisi daripada
diperlukan.
Untuk menganimasikan warna latar belakang tanpa batas, lihat mengulangi animasi bagian.
Menganimasikan ukuran composable
![Composable hijau yang menganimasikan perubahan ukurannya dengan lancar.](https://developer.android.com/static/develop/ui/compose/images/animations/animated_content_size.gif?authuser=9&hl=id)
Compose memungkinkan Anda menganimasikan ukuran composable dalam beberapa cara yang berbeda. Gunakan
animateContentSize()
untuk animasi di antara perubahan ukuran composable.
Misalnya, jika Anda memiliki kotak yang berisi teks yang dapat diperluas dari satu ke
beberapa baris, Anda dapat menggunakan Modifier.animateContentSize()
untuk
transisi:
var expanded by remember { mutableStateOf(false) } Box( modifier = Modifier .background(colorBlue) .animateContentSize() .height(if (expanded) 400.dp else 200.dp) .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { expanded = !expanded } ) { }
Anda juga dapat menggunakan AnimatedContent
, dengan SizeTransform
untuk mendeskripsikan
bagaimana perubahan ukuran harus dilakukan.
Menganimasikan posisi composable
![Composable hijau yang bergerak mulus ke bawah dan ke kanan](https://developer.android.com/static/develop/ui/compose/images/animations/animated_offset.gif?authuser=9&hl=id)
Untuk menganimasikan posisi composable, gunakan Modifier.offset{ }
yang dikombinasikan dengan
animateIntOffsetAsState()
.
var moved by remember { mutableStateOf(false) } val pxToMove = with(LocalDensity.current) { 100.dp.toPx().roundToInt() } val offset by animateIntOffsetAsState( targetValue = if (moved) { IntOffset(pxToMove, pxToMove) } else { IntOffset.Zero }, label = "offset" ) Box( modifier = Modifier .offset { offset } .background(colorBlue) .size(100.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { moved = !moved } )
Jika Anda ingin memastikan bahwa composable tidak digambar di atas atau di bawah
composable saat menganimasikan posisi atau ukuran, gunakan Modifier.layout{ }
. Ini
pengubah menyebarkan perubahan ukuran dan posisi ke induk, yang kemudian memengaruhi
anak-anak lainnya.
Misalnya, jika Anda memindahkan Box
dalam Column
dan turunan lainnya
harus dipindahkan saat Box
bergerak, sertakan informasi offset dengan
Modifier.layout{ }
sebagai berikut:
var toggled by remember { mutableStateOf(false) } val interactionSource = remember { MutableInteractionSource() } Column( modifier = Modifier .padding(16.dp) .fillMaxSize() .clickable(indication = null, interactionSource = interactionSource) { toggled = !toggled } ) { val offsetTarget = if (toggled) { IntOffset(150, 150) } else { IntOffset.Zero } val offset = animateIntOffsetAsState( targetValue = offsetTarget, label = "offset" ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) Box( modifier = Modifier .layout { measurable, constraints -> val offsetValue = if (isLookingAhead) offsetTarget else offset.value val placeable = measurable.measure(constraints) layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) { placeable.placeRelative(offsetValue) } } .size(100.dp) .background(colorGreen) ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) }
![2 kotak dengan kotak ke-2 menganimasikan posisi X,Y-nya, kotak ketiga merespons dengan memindahkan jumlah Y juga.](https://developer.android.com/static/develop/ui/compose/images/animations/animated_layout_modifier.gif?authuser=9&hl=id)
Modifier.layout{ }
Menganimasikan padding composable
![Composable hijau akan menjadi semakin kecil saat diklik, dengan padding dianimasikan](https://developer.android.com/static/develop/ui/compose/images/animations/animated_padding.gif?authuser=9&hl=id)
Untuk menganimasikan padding composable, gunakan animateDpAsState
yang dikombinasikan dengan
Modifier.padding()
:
var toggled by remember { mutableStateOf(false) } val animatedPadding by animateDpAsState( if (toggled) { 0.dp } else { 20.dp }, label = "padding" ) Box( modifier = Modifier .aspectRatio(1f) .fillMaxSize() .padding(animatedPadding) .background(Color(0xff53D9A1)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { toggled = !toggled } )
Menganimasikan elevasi composable
Untuk menganimasikan elevasi composable, gunakan animateDpAsState
yang dikombinasikan dengan
Modifier.graphicsLayer{ }
. Untuk perubahan elevasi yang terjadi sekali saja, gunakan
Modifier.shadow()
. Jika Anda menganimasikan bayangan, menggunakan
Pengubah Modifier.graphicsLayer{ }
adalah opsi yang berperforma lebih tinggi.
val mutableInteractionSource = remember { MutableInteractionSource() } val pressed = mutableInteractionSource.collectIsPressedAsState() val elevation = animateDpAsState( targetValue = if (pressed.value) { 32.dp } else { 8.dp }, label = "elevation" ) Box( modifier = Modifier .size(100.dp) .align(Alignment.Center) .graphicsLayer { this.shadowElevation = elevation.value.toPx() } .clickable(interactionSource = mutableInteractionSource, indication = null) { } .background(colorGreen) ) { }
Atau, gunakan composable Card
, dan tetapkan properti elevasi ke
nilai yang berbeda untuk setiap negara bagian.
Menganimasikan skala, terjemahan, atau rotasi teks
![Teks composable yang bertuliskan](https://developer.android.com/static/develop/ui/compose/images/animations/animated_text.gif?authuser=9&hl=id)
Saat menganimasikan skala, terjemahan, atau rotasi teks, setel textMotion
pada TextStyle
untuk TextMotion.Animated
. Hal ini memastikan performa
transisi antara
animasi teks. Gunakan Modifier.graphicsLayer{ }
untuk
menerjemahkan, memutar, atau menskalakan teks.
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 8f, animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "scale" ) Box(modifier = Modifier.fillMaxSize()) { Text( text = "Hello", modifier = Modifier .graphicsLayer { scaleX = scale scaleY = scale transformOrigin = TransformOrigin.Center } .align(Alignment.Center), // Text composable does not take TextMotion as a parameter. // Provide it via style argument but make sure that we are copying from current theme style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated) ) }
Menganimasikan warna teks
![Kata-kata](https://developer.android.com/static/develop/ui/compose/images/animations/animated_text_color.gif?authuser=9&hl=id)
Untuk menganimasikan warna teks, gunakan lambda color
pada composable BasicText
:
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val animatedColor by infiniteTransition.animateColor( initialValue = Color(0xFF60DDAD), targetValue = Color(0xFF4285F4), animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "color" ) BasicText( text = "Hello Compose", color = { animatedColor }, // ... )
Beralih antara jenis konten yang berbeda
![Layar hijau berkata](https://developer.android.com/static/develop/ui/compose/images/animations/animated_content_slower.gif?authuser=9&hl=id)
Gunakan AnimatedContent
untuk menganimasikan antar-composable yang berbeda, jika Anda
hanya ingin fade standar antar-composable, gunakan Crossfade
.
var state by remember { mutableStateOf(UiState.Loading) } AnimatedContent( state, transitionSpec = { fadeIn( animationSpec = tween(3000) ) togetherWith fadeOut(animationSpec = tween(3000)) }, modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { state = when (state) { UiState.Loading -> UiState.Loaded UiState.Loaded -> UiState.Error UiState.Error -> UiState.Loading } }, label = "Animated Content" ) { targetState -> when (targetState) { UiState.Loading -> { LoadingScreen() } UiState.Loaded -> { LoadedScreen() } UiState.Error -> { ErrorScreen() } } }
AnimatedContent
dapat disesuaikan untuk menampilkan berbagai jenis tombol enter dan
transisi keluar. Untuk informasi selengkapnya, baca dokumentasi tentang
AnimatedContent
atau baca postingan blog ini di
AnimatedContent
Menganimasikan momen saat bernavigasi ke tujuan yang berbeda
![Dua composable, satu warna hijau bertuliskan Landing dan satu warna biru bertuliskan Detail, yang dianimasikan dengan menggeser composable detail di atas composable landing.](https://developer.android.com/static/develop/ui/compose/images/animations/navigation_compose_animation.gif?authuser=9&hl=id)
Untuk menganimasikan transisi antar-composable saat menggunakan
artefak navigation-compose, tentukan enterTransition
,
exitTransition
pada composable. Anda juga dapat mengatur
animasi {i>default<i} untuk
digunakan untuk semua tujuan di NavHost
level teratas:
val navController = rememberNavController() NavHost( navController = navController, startDestination = "landing", enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { composable("landing") { ScreenLanding( // ... ) } composable( "detail/{photoUrl}", arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }), enterTransition = { fadeIn( animationSpec = tween( 300, easing = LinearEasing ) ) + slideIntoContainer( animationSpec = tween(300, easing = EaseIn), towards = AnimatedContentTransitionScope.SlideDirection.Start ) }, exitTransition = { fadeOut( animationSpec = tween( 300, easing = LinearEasing ) ) + slideOutOfContainer( animationSpec = tween(300, easing = EaseOut), towards = AnimatedContentTransitionScope.SlideDirection.End ) } ) { backStackEntry -> ScreenDetails( // ... ) } }
Ada banyak jenis transisi masuk dan keluar yang berlaku dampak yang berbeda terhadap konten yang masuk dan keluar, lihat dokumentasi untuk informasi lebih lanjut.
Mengulangi animasi
![Latar belakang hijau yang berubah menjadi latar belakang biru, tanpa batas dengan menganimasikan dua warna.](https://developer.android.com/static/develop/ui/compose/images/animations/animated_forever.gif?authuser=9&hl=id)
Menggunakan rememberInfiniteTransition
dengan infiniteRepeatable
animationSpec
untuk terus mengulangi animasi Anda. Ubah RepeatModes
menjadi
menentukan bagaimana harus bolak-balik.
Gunakan finiteRepeatable
untuk mengulangi beberapa kali.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Green, targetValue = Color.Blue, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(color) } ) { // your composable here }
Memulai animasi saat meluncurkan composable
LaunchedEffect
berjalan saat composable memasuki komposisi. Dimulai
animasi saat peluncuran composable, Anda dapat menggunakan ini untuk mendorong animasi
perubahan status. Menggunakan Animatable
dengan metode animateTo
untuk memulai
animasi saat peluncuran:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
Membuat animasi berurutan
![Empat lingkaran dengan panah hijau bergerak di antara masing-masing lingkaran, bergerak satu per satu.](https://developer.android.com/static/develop/ui/compose/images/animations/multiple_properties_sequential.gif?authuser=9&hl=id)
Menggunakan API coroutine Animatable
untuk menjalankan operasi berurutan atau serentak
animasi. Memanggil animateTo
di Animatable
satu demi satu penyebab lainnya
setiap animasi menunggu hingga animasi sebelumnya selesai sebelum melanjutkan .
Ini karena merupakan fungsi penangguhan.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
Membuat animasi serentak
![Tiga lingkaran dengan panah hijau bergerak ke masing-masing lingkaran, bergerak bersama-sama pada saat yang sama.](https://developer.android.com/static/develop/ui/compose/images/animations/multiple_properties.gif?authuser=9&hl=id)
Gunakan API coroutine (Animatable#animateTo()
atau animate
), atau
Transition
API untuk menghasilkan animasi serentak. Jika Anda menggunakan beberapa
meluncurkan fungsi dalam konteks coroutine, meluncurkan animasi pada
waktu:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
Anda dapat menggunakan updateTransition
API untuk menggunakan status yang sama untuk mengemudi
banyak animasi properti yang
berbeda secara bersamaan. Contoh di bawah ini membuat animasi
dua properti yang dikontrol oleh perubahan status, rect
dan borderWidth
:
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "rect") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "borderWidth") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Mengoptimalkan performa animasi
Animasi di Compose dapat menyebabkan masalah performa. Hal ini karena sifat tentang animasi: memindahkan atau mengubah piksel di layar dengan cepat, per frame untuk menciptakan ilusi gerakan.
Pertimbangkan berbagai fase Compose: komposisi, tata letak, dan menggambar. Jika animasi Anda mengubah fase tata letak. Animasi ini mengharuskan semua composable yang terpengaruh menata ulang dan menggambar ulang. Jika animasi Anda muncul di fase menggambar, itu dengan secara default berperforma lebih baik daripada jika Anda menjalankan animasi di tata letak karena akan ada lebih sedikit pekerjaan yang harus dilakukan secara keseluruhan.
Untuk memastikan aplikasi Anda melakukan operasi seminimal mungkin saat menganimasikan, pilih lambda
dari Modifier
jika memungkinkan. Ini melewati rekomposisi dan melakukan
animasi di luar fase komposisi, jika tidak, gunakan
Modifier.graphicsLayer{ }
, karena pengubah ini selalu berjalan dalam gambar
fase sebelumnya. Untuk informasi selengkapnya tentang hal ini, lihat bagian menunda pembacaan di
dokumentasi performa.
Mengubah pengaturan waktu animasi
Compose secara default menggunakan animasi spring untuk sebagian besar animasi. Spring, atau
animasi berbasis fisika, terasa lebih alami. Mereka juga dapat diinterupsi ketika
mereka memperhitungkan kecepatan objek saat ini, bukan waktu tetap.
Jika Anda ingin mengganti gaya default, semua API animasi yang ditunjukkan di atas
dapat menyetel animationSpec
untuk menyesuaikan cara animasi berjalan,
apakah Anda ingin program ini berjalan
selama durasi tertentu atau lebih {i>bouncy<i}.
Berikut adalah ringkasan dari berbagai opsi animationSpec
:
spring
: Animasi berbasis fisika, default untuk semua animasi. Anda dapat mengubah kekakuan atau rasio redaman untuk mendapatkan animasi yang berbeda tampilan dan nuansa.tween
(kependekan dari between): Animasi berbasis durasi, animasi antara dua nilai dengan {i>function<i}Easing
.keyframes
: Spesifikasi untuk menentukan nilai pada poin utama tertentu dalam animasi.repeatable
: Spesifikasi berbasis durasi yang berjalan beberapa kali, ditentukan olehRepeatMode
.infiniteRepeatable
: Spesifikasi berbasis durasi yang berjalan selamanya.snap
: Mengepaskan ke nilai akhir secara instan tanpa animasi apa pun.
![Tulis {i>alt text<i} Anda di sini](/static/develop/ui/compose/images/animations/animated_spec_set.gif)
Baca dokumentasi lengkap untuk informasi selengkapnya tentang animasiSpecs.
Referensi lainnya
Untuk contoh animasi seru lainnya di Compose, lihat gambar berikut:
- 5 animasi cepat di Compose
- Membuat Jellyfish (Ubur-ubur) bergerak di Compose
- Menyesuaikan
AnimatedContent
di Compose - Easing ke fungsi Easing di Compose