Pengubah dan composable animasi

Compose dilengkapi dengan composable dan pengubah bawaan untuk menangani kasus penggunaan animasi umum.

Composable animasi bawaan

Compose menyediakan beberapa composable yang menganimasikan kemunculan, hilangnya, dan perubahan tata letak konten.

Menganimasikan pemunculan dan penghilangan

Komposisi hijau yang menampilkan dan menyembunyikan dirinya sendiri
Gambar 1. Menganimasikan muncul dan hilangnya item dalam kolom.

Composable AnimatedVisibility menganimasikan muncul dan hilangnya kontennya.

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
    // ...
}

Secara default, konten muncul dengan menjadi jelas dan meluas, serta menghilang dengan memudar dan menyusut. Sesuaikan transisi ini dengan menentukan objek EnterTransition dan ExitTransition.

var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
    visible = visible,
    enter = slideInVertically {
        // Slide in from 40 dp from the top.
        with(density) { -40.dp.roundToPx() }
    } + expandVertically(
        // Expand from the top.
        expandFrom = Alignment.Top
    ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
    ),
    exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
    Text(
        "Hello",
        Modifier
            .fillMaxWidth()
            .height(200.dp)
    )
}

Seperti yang ditunjukkan pada contoh sebelumnya, Anda dapat menggabungkan beberapa objek EnterTransition atau ExitTransition dengan operator +, dan masing-masing menerima parameter opsional untuk menyesuaikan perilakunya. Lihat halaman referensi untuk informasi selengkapnya.

Contoh transisi masuk dan keluar

EnterTransition ExitTransition
fadeIn
Elemen UI secara bertahap muncul.
fadeOut
Elemen UI secara bertahap memudar dari tampilan.
slideIn
Elemen UI meluncur ke dalam tampilan dari luar layar.
slideOut
Elemen UI meluncur keluar dari tampilan di luar layar.
slideInHorizontally
Elemen UI meluncur secara horizontal ke dalam tampilan.
slideOutHorizontally
Elemen UI meluncur secara horizontal ke luar tampilan.
slideInVertically
Elemen UI meluncur secara vertikal ke dalam tampilan.
slideOutVertically
Elemen UI meluncur secara vertikal ke luar tampilan.
scaleIn
Elemen UI diperbesar dan ditampilkan.
scaleOut
Elemen UI diperkecil dan keluar dari tampilan.
expandIn
Elemen UI diperluas ke dalam tampilan dari titik pusat.
shrinkOut
Elemen UI menyusut keluar dari tampilan ke titik tengah.
expandHorizontally
Elemen UI meluas secara horizontal ke dalam tampilan.
shrinkHorizontally
Elemen UI menyusut secara horizontal ke luar tampilan.
expandVertically
Elemen UI diperluas secara vertikal ke dalam tampilan.
shrinkVertically
Elemen UI menyusut secara vertikal ke luar tampilan.

AnimatedVisibility juga menawarkan varian yang memerlukan argumen MutableTransitionState. Hal ini memungkinkan Anda memicu animasi segera setelah composable AnimatedVisibility ditambahkan ke hierarki komposisi. Hal ini juga berguna untuk mengamati status animasi.

// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember {
    MutableTransitionState(false).apply {
        // Start the animation immediately.
        targetState = true
    }
}
Column {
    AnimatedVisibility(visibleState = state) {
        Text(text = "Hello, world!")
    }

    // Use the MutableTransitionState to know the current animation state
    // of the AnimatedVisibility.
    Text(
        text = when {
            state.isIdle && state.currentState -> "Visible"
            !state.isIdle && state.currentState -> "Disappearing"
            state.isIdle && !state.currentState -> "Invisible"
            else -> "Appearing"
        }
    )
}

Menganimasikan masuk dan keluar untuk turunan

Konten dalam AnimatedVisibility (turunan langsung atau tidak langsung) dapat menggunakan pengubah animateEnterExit untuk menentukan perilaku animasi yang berbeda bagi setiap turunan. Efek visual untuk setiap turunan ini adalah kombinasi animasi yang ditetapkan pada composable AnimatedVisibility serta animasi masuk dan keluar turunan itu sendiri.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(
        Modifier
            .fillMaxSize()
            .background(Color.DarkGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    // Slide in/out the inner box.
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
            // Content of the notification…
        }
    }
}

Dalam beberapa kasus, Anda mungkin ingin AnimatedVisibility tidak menerapkan animasi sama sekali sehingga turunan dapat memiliki animasinya sendiri dengan menggunakan animateEnterExit. Untuk mencapai hal ini, tentukan EnterTransition.None dan ExitTransition.None pada composable AnimatedVisibility.

Menambahkan animasi kustom

Jika Anda ingin menambahkan efek animasi kustom di luar animasi masuk dan keluar bawaan, akses instance Transition dasar menggunakan properti transition di dalam lambda konten untuk AnimatedVisibility. Semua status animasi yang ditambahkan ke instance Transition akan berjalan bersamaan dengan animasi masuk dan keluar dari AnimatedVisibility. AnimatedVisibility menunggu hingga semua animasi di Transition selesai sebelum menghapus kontennya. Untuk animasi keluar yang dibuat terpisah dari Transition (seperti menggunakan animate*AsState), AnimatedVisibility tidak akan dapat memperhitungkannya, dan oleh karena itu dapat menghapus composable konten sebelum selesai.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) { // this: AnimatedVisibilityScope
    // Use AnimatedVisibilityScope#transition to add a custom animation
    // to the AnimatedVisibility.
    val background by transition.animateColor(label = "color") { state ->
        if (state == EnterExitState.Visible) Color.Blue else Color.Gray
    }
    Box(
        modifier = Modifier
            .size(128.dp)
            .background(background)
    )
}

Untuk mempelajari lebih lanjut cara menggunakan Transition untuk mengelola animasi, lihat Menganimasikan beberapa properti secara bersamaan dengan transisi.

Menganimasikan berdasarkan status target

Composable AnimatedContent akan menganimasikan kontennya saat composable tersebut berubah berdasarkan status target.

Row {
    var count by remember { mutableIntStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(
        targetState = count,
        label = "animated content"
    ) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

Secara default, konten awal akan memudar, lalu konten target akan makin jelas (perilaku ini disebut memudar). Anda dapat menyesuaikan perilaku animasi ini dengan menentukan objek ContentTransform ke parameter transitionSpec. Anda dapat membuat instance ContentTransform dengan menggabungkan objek EnterTransition dengan objek ExitTransition menggunakan fungsi infix with. Anda dapat menerapkan SizeTransform ke objek ContentTransform dengan melampirkannya dengan fungsi infix using.

AnimatedContent(
    targetState = count,
    transitionSpec = {
        // Compare the incoming number with the previous number.
        if (targetState > initialState) {
            // If the target number is larger, it slides up and fades in
            // while the initial (smaller) number slides up and fades out.
            slideInVertically { height -> height } + fadeIn() togetherWith
                slideOutVertically { height -> -height } + fadeOut()
        } else {
            // If the target number is smaller, it slides down and fades in
            // while the initial number slides down and fades out.
            slideInVertically { height -> -height } + fadeIn() togetherWith
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }, label = "animated content"
) { targetCount ->
    Text(text = "$targetCount")
}

EnterTransition akan menentukan cara konten target akan muncul, dan ExitTransition akan menentukan cara konten awal akan menghilang. Selain semua fungsi EnterTransition dan ExitTransition yang tersedia untuk AnimatedVisibility, AnimatedContent menawarkan slideIntoContainer dan slideOutOfContainer. Fungsi ini merupakan alternatif yang praktis untuk slideInHorizontally/Vertically dan slideOutHorizontally/Vertically yang menghitung jarak slide berdasarkan pada ukuran konten awal dan konten target dari konten AnimatedContent.

SizeTransform menentukan cara ukuran akan dianimasikan antara konten awal dan target. Anda memiliki akses ke ukuran awal dan ukuran target saat membuat animasi. SizeTransform juga mengontrol apakah konten harus dipotong ke ukuran komponen selama animasi.

var expanded by remember { mutableStateOf(false) }
Surface(
    color = MaterialTheme.colorScheme.primary,
    onClick = { expanded = !expanded }
) {
    AnimatedContent(
        targetState = expanded,
        transitionSpec = {
            fadeIn(animationSpec = tween(150, 150)) togetherWith
                fadeOut(animationSpec = tween(150)) using
                SizeTransform { initialSize, targetSize ->
                    if (targetState) {
                        keyframes {
                            // Expand horizontally first.
                            IntSize(targetSize.width, initialSize.height) at 150
                            durationMillis = 300
                        }
                    } else {
                        keyframes {
                            // Shrink vertically first.
                            IntSize(initialSize.width, targetSize.height) at 150
                            durationMillis = 300
                        }
                    }
                }
        }, label = "size transform"
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

Menganimasikan transisi masuk dan keluar turunan

Sama seperti AnimatedVisibility, pengubah animateEnterExit tersedia di dalam lambda konten AnimatedContent. Gunakan ini untuk menerapkan EnterAnimation dan ExitAnimation ke setiap turunan langsung atau tidak langsung secara terpisah.

Menambahkan animasi kustom

Sama seperti AnimatedVisibility, kolom transition tersedia di dalam lambda konten AnimatedContent. Gunakan ini untuk membuat efek animasi kustom yang berjalan secara bersamaan dengan transisi AnimatedContent. Lihat updateTransition untuk mengetahui detailnya.

Menganimasikan transisi antara dua tata letak

Crossfade membuat animasi di antara dua tata letak dengan animasi crossfade. Dengan mengganti nilai yang diteruskan ke parameter current, konten dialihkan dengan animasi crossfade.

var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage, label = "cross fade") { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}

Pengubah animasi bawaan

Compose menyediakan pengubah untuk menganimasikan perubahan tertentu secara langsung pada composable.

Menganimasikan perubahan ukuran composable

Composable hijau menganimasikan perubahan ukurannya dengan lancar.
Gambar 2. Composable yang bergerak lancar antara ukuran kecil dan lebih besar

Pengubah animateContentSize menganimasikan perubahan ukuran.

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
        }

) {
}

Animasi item daftar

Jika Anda ingin menganimasikan pengurutan ulang item di dalam daftar atau petak Lambat, lihat dokumentasi animasi item tata letak Lambat.