Pengubah dan composable animasi

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

Composable animasi bawaan

Menganimasikan kemunculan dan penghilangan dengan AnimatedVisibility

Composable hijau yang menampilkan dan menyembunyikan dirinya sendiri
Gambar 1. Menganimasikan kemunculan 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. Transisi dapat disesuaikan dengan menentukan 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 Anda lihat pada contoh di atas, Anda dapat menggabungkan beberapa objek EnterTransition atau ExitTransition dengan operator +, dan masing-masing menerima parameter opsional untuk menyesuaikan perilakunya. Lihat referensi untuk informasi selengkapnya.

Contoh EnterTransition dan ExitTransition

EnterTransition ExitTransition
fadeIn
animasi menjadi jelas
fadeOut
animasi memudar
slideIn
animasi geser masuk
slideOut
animasi geser keluar
slideInHorizontally
animasi geser masuk secara horizontal
slideOutHorizontally
animasi geser keluar secara horizontal
slideInVertically
animasi geser masuk secara vertikal
slideOutVertically
animasi geser keluar secara vertikal
scaleIn
animasi membesar
scaleOut
animasi mengecil
expandIn
animasi meluas
shrinkOut
animasi menciut
expandHorizontally
animasi meluas secara horizontal
shrinkHorizontally
animasi menciut secara horizontal
expandVertically
animasi meluas secara vertikal
shrinkVertically
animasi menciut secara vertikal

AnimatedVisibility juga menawarkan varian yang memerlukan MutableTransitionState. Hal ini memungkinkan Anda untuk memicu animasi segera setelah 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 melalui 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)
    )
}

Lihat updateTransition untuk mengetahui detail tentang Transition.

Menganimasikan berdasarkan status target dengan AnimatedContent

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")
    }
}

Perhatikan bahwa Anda harus selalu menggunakan parameter lambda dan mencerminkannya ke konten. API menggunakan nilai ini sebagai kunci untuk mengidentifikasi konten yang saat ini ditampilkan.

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 ContentTransform dengan mengombinasikan EnterTransition dan ExitTransition menggunakan fungsi infix with. Anda dapat menerapkan SizeTransform ke 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 antara dua tata letak dengan Crossfade

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

Menganimasikan perubahan ukuran composable dengan animateContentSize

Composable hijau menganimasikan perubahan ukurannya dengan lancar.
Gambar 2. Composable yang dapat dianimasikan dengan lancar antara ukuran kecil dan 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.