Compose memiliki banyak mekanisme animasi bawaan dan bisa membingungkan karena mengetahui harus mana yang harus dipilih. Berikut adalah daftar kasus penggunaan animasi yang umum. Untuk mengetahui informasi yang lebih mendetail tentang serangkaian lengkap opsi API yang berbeda yang tersedia untuk Anda, baca dokumentasi Animasi Compose selengkapnya.
Menganimasikan properti composable umum
Compose menyediakan API yang praktis yang memungkinkan Anda mengatasi banyak kasus penggunaan animasi umum. Bagian ini menunjukkan cara menganimasikan properti umum composable.
Menganimasikan kemunculan / penghilangan
Gunakan AnimatedVisibility
untuk menyembunyikan atau menampilkan Composable. Turunan di dalam
AnimatedVisibility
dapat menggunakan Modifier.animateEnterExit()
untuk transisi masuk
atau keluarnya sendiri.
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 perilaku
composable saat muncul dan menghilang. Baca dokumentasi
lengkap untuk mengetahui informasi selengkapnya.
Opsi lain untuk menganimasikan visibilitas composable adalah menganimasikan
alpha 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 memiliki peringatan bahwa composable tetap
berada dalam komposisi dan terus menempati ruang tempatnya ditata. Hal ini
dapat menyebabkan pembaca layar dan mekanisme aksesibilitas lainnya masih mempertimbangkan
item di layar. Di sisi lain, AnimatedVisibility
pada akhirnya akan menghapus
item dari komposisi.
Menganimasikan warna latar belakang
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Opsi ini memiliki performa yang lebih baik daripada menggunakan Modifier.background()
.
Modifier.background()
dapat diterima untuk setelan warna satu kali, tetapi saat
mengoanimasi warna dari waktu ke waktu, hal ini dapat menyebabkan lebih banyak rekomposisi daripada
yang diperlukan.
Untuk menganimasikan warna latar belakang tanpa batas, lihat mengulang bagian animasi.
Menganimasikan ukuran composable
Compose memungkinkan Anda menganimasikan ukuran composable dengan beberapa cara. Gunakan
animateContentSize()
untuk animasi di antara perubahan ukuran composable.
Misalnya, jika Anda memiliki kotak yang berisi teks yang dapat diperluas dari satu hingga
beberapa baris, Anda dapat menggunakan Modifier.animateContentSize()
untuk mendapatkan transisi
yang lebih lancar:
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
cara perubahan ukuran dilakukan.
Menganimasikan posisi composable
Untuk menganimasikan posisi composable, gunakan Modifier.offset{ }
yang digabungkan 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 lain saat menganimasikan posisi atau ukuran, gunakan Modifier.layout{ }
. Pengubah
ini menyebarkan perubahan ukuran dan posisi ke induk, yang kemudian memengaruhi
turunan lainnya.
Misalnya, jika Anda memindahkan Box
dalam Column
dan turunan lainnya
harus bergerak saat Box
bergerak, sertakan informasi offset dengan
Modifier.layout{ }
seperti 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) ) }
Menganimasikan padding composable
Untuk menganimasikan padding composable, gunakan animateDpAsState
yang digabungkan 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 satu kali, gunakan
Modifier.shadow()
. Jika Anda menganimasikan bayangan, menggunakan
pengubah Modifier.graphicsLayer{ }
adalah opsi yang lebih berperforma.
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 per status.
Menganimasikan skala, terjemahan, atau rotasi teks
Saat menganimasikan skala, terjemahan, atau rotasi teks, tetapkan parameter textMotion
di TextStyle
ke TextMotion.Animated
. Hal ini memastikan transisi
yang lebih lancar di 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
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 antar-jenis konten
Gunakan AnimatedContent
untuk menganimasikan antar-composable yang berbeda. Jika Anda
hanya ingin memudar secara 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 transisi masuk dan
keluar. Untuk mengetahui informasi selengkapnya, baca dokumentasi tentang
AnimatedContent
atau baca postingan blog ini tentang
AnimatedContent
.
Menganimasikan saat menavigasi ke tujuan yang berbeda
Untuk menganimasikan transisi antar-composable saat menggunakan
artefak navigation-compose, tentukan enterTransition
dan
exitTransition
pada composable. Anda juga dapat menetapkan animasi default yang akan
digunakan untuk semua tujuan di NavHost
level atas:
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 menerapkan efek yang berbeda pada konten masuk dan keluar, lihat dokumentasi untuk mengetahui informasi selengkapnya.
Mengulangi animasi
Gunakan rememberInfiniteTransition
dengan infiniteRepeatable
animationSpec
untuk terus mengulangi animasi. Ubah RepeatModes
untuk
menentukan caranya 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. Fungsi ini memulai
animasi saat meluncurkan composable, Anda dapat menggunakannya untuk mendorong perubahan
status animasi. 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
Gunakan API coroutine Animatable
untuk menjalankan animasi
berurutan atau serentak. Memanggil animateTo
pada Animatable
satu per satu menyebabkan
setiap animasi menunggu 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
Gunakan API coroutine (Animatable#animateTo()
atau animate
), atau
Transition
API untuk mencapai animasi serentak. Jika Anda menggunakan beberapa
fungsi peluncuran dalam konteks coroutine, fungsi tersebut akan meluncurkan animasi secara
bersamaan:
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 guna mendorong
banyak animasi properti yang berbeda secara bersamaan. Contoh di bawah menganimasikan 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 disebabkan oleh sifat animasi: menggerakkan atau mengubah piksel di layar dengan cepat, frame per frame untuk menciptakan ilusi gerakan.
Pertimbangkan berbagai fase Compose: komposisi, tata letak, dan gambar. Jika animasi Anda mengubah fase tata letak, semua composable yang terpengaruh harus menata ulang dan menggambar ulang. Jika animasi Anda terjadi dalam fase gambar, secara default animasi akan memiliki performa yang lebih baik daripada jika Anda menjalankan animasi dalam fase tata letak, karena animasi akan memiliki lebih sedikit pekerjaan yang harus dilakukan secara keseluruhan.
Untuk memastikan aplikasi Anda melakukan sesedikit mungkin saat menganimasikan, pilih versi lambda
Modifier
jika memungkinkan. Tindakan ini akan melewati rekomposisi dan menjalankan
animasi di luar fase komposisi, jika tidak, gunakan
Modifier.graphicsLayer{ }
, karena pengubah ini selalu berjalan dalam fase
gambar. Untuk informasi selengkapnya tentang hal ini, lihat bagian menunda pembacaan dalam
dokumentasi performa.
Mengubah pengaturan waktu animasi
Compose secara default menggunakan animasi pegas untuk sebagian besar animasi. Pegas, atau
animasi berbasis fisika, terasa lebih alami. Fungsi ini juga dapat diinterupsi karena memperhitungkan kecepatan objek saat ini, bukan waktu yang tetap.
Jika Anda ingin mengganti gaya default, semua API animasi yang ditunjukkan di atas
memiliki kemampuan untuk menyetel animationSpec
guna menyesuaikan cara animasi berjalan,
baik Anda ingin animasi tersebut dijalankan selama durasi tertentu atau menjadi lebih goyang.
Berikut adalah ringkasan berbagai opsi animationSpec
:
spring
: Animasi berbasis fisika, yang merupakan default untuk semua animasi. Anda dapat mengubah stiffness atau dampingRatio untuk mendapatkan tampilan dan nuansa animasi yang berbeda.tween
(singkatan dari antara): Animasi berbasis durasi, menganimasikan antara dua nilai dengan fungsiEasing
.keyframes
: Spesifikasi untuk menentukan nilai pada titik kunci tertentu dalam animasi.repeatable
: Spesifikasi berbasis durasi yang berjalan beberapa kali, yang ditentukan olehRepeatMode
.infiniteRepeatable
: Spesifikasi berbasis durasi yang berjalan selamanya.snap
: Mengepaskan ke nilai akhir secara instan tanpa animasi apa pun.
Baca dokumentasi selengkapnya untuk mengetahui informasi selengkapnya tentang animationSpecs.
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