Compose memiliki banyak mekanisme animasi bawaan dan mungkin sulit untuk mengetahui mana yang harus dipilih. Berikut adalah daftar kasus penggunaan animasi umum. Untuk informasi lebih mendetail tentang kumpulan lengkap opsi API yang berbeda yang tersedia untuk Anda, baca dokumentasi lengkap Animasi Compose dokumentasi.
Menganimasikan properti composable umum
Compose menyediakan API praktis yang memungkinkan Anda menyelesaikan 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 keluar mereka 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 dari 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
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 dengan peringatan bahwa composable tetap dalam komposisi dan terus menempati ruang yang ditata letaknya. 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 menganimasikan warna dari waktu ke waktu, hal ini dapat menyebabkan lebih banyak rekomposisi daripada yang diperlukan.
Untuk menganimasikan warna latar belakang tanpa batas, lihat mengulang animasi bagian.
Menganimasikan ukuran composable
Compose memungkinkan Anda menganimasikan ukuran composable dengan beberapa cara yang berbeda. Gunakan
animateContentSize() untuk animasi 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 mencapai 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 menjelaskan
cara perubahan ukuran harus dilakukan.
Menganimasikan posisi composable
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 lain saat menganimasikan posisi atau ukuran, gunakan Modifier.layout{ }. Pengubah ini menyebarkan perubahan ukuran dan posisi ke induk, yang kemudian memengaruhi elemen turunan lainnya.
Misalnya, jika Anda memindahkan Box dalam Column dan elemen turunan lainnya perlu dipindahkan saat Box dipindahkan, 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) ) }
Modifier.layout{ }Menganimasikan padding composable
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 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, translasi, atau rotasi teks
Saat menganimasikan skala, translasi, atau rotasi teks, tetapkan textMotion
parameter pada TextStyle ke TextMotion.Animated. Hal ini memastikan transisi yang lebih lancar 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 antara berbagai jenis konten
Gunakan AnimatedContent untuk menganimasikan antara composable yang berbeda. Jika Anda
hanya ingin fade standar antara 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 tentang
AnimatedContent ini.
Menganimasikan saat menavigasi ke tujuan yang berbeda
Untuk menganimasikan transisi antara 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 tingkat 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 berbeda pada konten masuk dan keluar. Lihat dokumentasinya untuk mengetahui informasi selengkapnya.
Mengulang animasi
Gunakan rememberInfiniteTransition dengan infiniteRepeatable
animationSpec untuk terus mengulang animasi. Ubah RepeatModes untuk menentukan cara animasi harus bolak-balik.
Gunakan repeatable untuk mengulang sejumlah 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 peluncuran composable
LaunchedEffect berjalan saat composable memasuki komposisi. Animasi ini memulai animasi saat peluncuran 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 melakukan animasi berurutan atau serentak. Memanggil animateTo pada Animatable satu demi satu menyebabkan setiap animasi menunggu animasi sebelumnya selesai sebelum melanjutkan .
Hal 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 pada saat yang sama:
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 pada saat yang sama. Contoh berikut 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: memindahkan atau mengubah piksel di layar dengan cepat, frame demi 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 ditata ulang dan digambar ulang. Jika animasi Anda terjadi dalam fase gambar, animasi tersebut secara default akan lebih berperforma daripada jika Anda menjalankan animasi dalam fase tata letak, karena animasi tersebut akan memiliki lebih sedikit pekerjaan secara keseluruhan.
Untuk memastikan aplikasi Anda melakukan sesedikit mungkin saat menganimasikan, pilih versi lambda Modifier jika memungkinkan. Hal ini akan melewati rekomposisi dan melakukan
animasi di luar fase komposisi. Jika tidak, gunakan
Modifier.graphicsLayer{ }, karena pengubah ini selalu berjalan dalam fase gambar
. Untuk mengetahui informasi selengkapnya tentang hal ini, lihat bagian penangguhan pembacaan dalam
dokumentasi performa.
Mengubah waktu animasi
Compose secara default menggunakan animasi pegas untuk sebagian besar animasi. Pegas, atau animasi berbasis fisika, terasa lebih alami. Animasi ini juga dapat diinterupsi karena mempertimbangkan kecepatan objek saat ini, bukan waktu tetap.
Jika Anda ingin mengganti default, semua API animasi yang ditunjukkan sebelumnya memiliki kemampuan untuk menetapkan animationSpec guna menyesuaikan cara animasi berjalan, baik Anda ingin animasi berjalan selama durasi tertentu atau lebih elastis.
Berikut adalah ringkasan berbagai opsi animationSpec:
spring: Animasi berbasis fisika, default untuk semua animasi. Anda dapat mengubah kekakuan atau dampingRatio untuk mencapai tampilan dan nuansa animasi yang berbeda.tween(singkatan dari between): Animasi berbasis durasi, dianimasikan 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: Langsung beralih ke nilai akhir tanpa animasi apa pun.
Baca dokumentasi lengkap untuk mengetahui informasi selengkapnya tentang animationSpecs.
Referensi lainnya
Untuk contoh animasi seru lainnya di Compose, lihat hal berikut:
- 5 animasi cepat di Compose
- Membuat Jellyfish (Ubur-ubur) bergerak di Compose
- Menyesuaikan
AnimatedContentdi Compose - Easing ke fungsi Easing di Compose