Menganimasikan satu nilai dengan animate*AsState
Fungsi animate*AsState
adalah API animasi yang paling 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.
Berikut contoh menganimasikan alfa menggunakan API ini. Hanya 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 alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
Perhatikan bahwa 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 mandiri, Compose menyediakan fungsi animate*AsState
untuk Float
,
Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
, dan
IntSize
. Anda dapat dengan mudah 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 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 memastikan 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
. Ini dapat digunakan sebagai sinyal
apakah transisi telah selesai.
Kita terkadang ingin memiliki status awal yang berbeda dengan status target pertama. Kita dapat menggunakan updateTransition
dengan MutableTransitionState
untuk mencapainya. Misalnya, memungkinkan kita 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 akan
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 transisi masuk/keluar sesuai kebutuhan saat
targetState
Transition
telah berubah. Fungsi ekstensi ini memungkinkan semua
animasi enter/exit/sizeTransform yang akan bersifat internal ke
AnimatedVisibility
/AnimatedContent
diangkat ke dalam Transition
.
Dengan fungsi ekstensi ini, perubahan status AnimatedVisibility
/AnimatedContent
dapat diamati 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 sederhana, menentukan animasi transisi dalam composable yang sama dengan UI adalah opsi yang sangat 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. Penerapan transisi dapat diekstrak ke fungsi terpisah yang baru. Pola ini berguna saat ada kebutuhan untuk 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 yang 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
. Animasi turunan dapat ditambahkan dengan
animateColor
, animatedFloat
, atau animatedValue
. 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) )
API animasi tingkat rendah
Semua API animasi tingkat tinggi yang disebutkan di bagian sebelumnya dibuat berdasarkan fondasi API animasi tingkat rendah.
Fungsi animate*AsState
adalah API yang paling sederhana, yang merender perubahan nilai instan sebagai nilai animasi. Ini didukung oleh Animatable
, yang merupakan API berbasis coroutine untuk menganimasikan nilai tunggal. updateTransition
membuat objek transisi yang dapat mengelola beberapa nilai animasi dan menjalankannya berdasarkan perubahan status. 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 animasi ini dapat dibuat di luar komposisi.
Semua API ini didasarkan pada Animation
API yang lebih mendasar. Meskipun sebagian besar aplikasi tidak akan berinteraksi langsung dengan Animation
, beberapa kemampuan penyesuaian untuk Animation
tersedia 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 melalui
animateTo
. Ini adalah API yang mencadangkan penerapan animate*AsState
.
Ini memastikan kelanjutan yang konsisten dan pengalaman eksklusif, yang berarti bahwa perubahan nilai selalu berkelanjutan dan setiap animasi yang sedang berlangsung akan dibatalkan.
Banyak fitur Animatable
, termasuk animateTo
, disediakan sebagai fungsi penangguhan. Ini berarti bahwa fitur-fitur tersebut harus digabungkan 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 di atas, kita 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 ada animasi yang berlangsung saat nilai diubah, animasi akan dibatalkan, dan animasi baru dimulai dari nilai snapshot saat ini dengan kecepatan saat ini.
Ini adalah implementasi animasi yang mencadangkan API animate*AsState
yang disebutkan di bagian sebelumnya. Dibandingkan dengan animate*AsState
, menggunakan
Animatable
secara langsung akan memberi kita kontrol yang lebih mendetail. Pertama,
Animatable
dapat memiliki nilai awal yang berbeda dari nilai target pertamanya.
Misalnya, contoh kode di atas menampilkan kotak abu-abu terlebih dahulu, yang segera mulai beranimasi menjadi hijau atau merah. Kedua, Animatable
memberikan lebih banyak
operasi pada nilai konten, yaitu snapTo
dan animateDecay
. snapTo
segera menyetel nilai saat ini ke nilai target. Hal ini berguna saat
animasi itu sendiri bukan satu-satunya sumber ketepatan dan harus disinkronkan dengan status
lain, seperti peristiwa sentuh. animateDecay
memulai animasi yang melambat
dari kecepatan yang ditentukan. Ini berguna untuk menerapkan perilaku fling. Lihat
Gestur dan animasi untuk informasi selengkapnya.
Secara mandiri, Animatable
mendukung Float
dan Color
, namun semua jenis data dapat digunakan dengan menyediakan TwoWayConverter
. Lihat
AnimationVector untuk mengetahui informasi selengkapnya.
Anda dapat menyesuaikan spesifikasi animasi dengan menyediakan AnimationSpec
.
Lihat AnimationSpec untuk informasi selengkapnya.
Animation
: Animasi yang dikontrol secara manual
Animation
adalah Animation API level terendah yang tersedia. Banyak animasi
yang telah kita lihat sejauh ini dibuat di atas Animasi. Ada dua subjenis Animation
:
TargetBasedAnimation
dan DecayAnimation
.
Animation
hanya boleh digunakan untuk mengontrol waktu animasi secara manual.
Animation
bersifat stateless, dan tidak memiliki konsep siklus proses apa pun. Hal ini
berfungsi sebagai mesin hitung animasi yang digunakan pada API dengan tingkat yang lebih tinggi.
TargetBasedAnimation
API lain mencakup sebagian besar kasus penggunaan, namun penggunaan TargetBasedAnimation
secara langsung
memungkinkan Anda untuk mengontrol waktu pemutaran animasi sendiri. Pada contoh di bawah,
waktu pemutaran TargetAnimation
dikontrol 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 disetel oleh initialVelocityVector
dan melambat seiring waktu.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Menyesuaikan animasi {:#customize-animations}
- Animasi di Compose
- Pengubah dan composable animasi