Menyesuaikan transisi elemen bersama

Untuk menyesuaikan cara animasi transisi elemen bersama berjalan, ada beberapa parameter yang dapat digunakan untuk mengubah cara transisi elemen bersama.

Spesifikasi animasi

Untuk mengubah spesifikasi animasi yang digunakan untuk pergerakan ukuran dan posisi, Anda dapat menentukan parameter boundsTransform yang berbeda di Modifier.sharedElement(). Ini memberikan posisi Rect awal dan target posisi Rect.

Misalnya, untuk membuat teks dalam contoh sebelumnya agar bergerak dengan gerakan lengkungan, tentukan parameter boundsTransform untuk menggunakan spesifikasi keyframes:

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

Anda dapat menggunakan AnimationSpec. Contoh ini menggunakan spesifikasi keyframes.

Gambar 1. Contoh yang menampilkan parameter boundsTransform yang berbeda

Mode ubah ukuran

Saat menganimasikan antara dua batas bersama, Anda dapat menetapkan parameter resizeMode ke RemeasureToBounds atau ScaleToBounds. Parameter ini menentukan cara elemen bersama bertransisi di antara dua status. ScaleToBounds terlebih dahulu mengukur tata letak turunan dengan batasan Lookahead (atau target). Kemudian, tata letak stabil turunan akan diskalakan agar sesuai dengan batas bersama. ScaleToBounds dapat dianggap sebagai "skala grafis" antara status.

Sementara itu, RemeasureToBounds mengukur ulang dan menata ulang tata letak turunan sharedBounds dengan batasan tetap animasi berdasarkan ukuran target. Pengukuran ulang dipicu oleh perubahan ukuran batas, yang berpotensi terjadi di setiap frame.

Untuk composable Text, ScaleToBounds direkomendasikan karena akan menghindari tata letak ulang dan perubahan posisi/geometri teks ke baris yang berbeda. Untuk batas yang memiliki rasio aspek yang berbeda, dan jika Anda menginginkan kontinuitas yang lancar antara dua elemen bersama, sebaiknya gunakan RemeasureToBounds.

Perbedaan antara dua mode ubah ukuran dapat dilihat dalam contoh berikut:

ScaleToBounds

RemeasureToBounds

Langsung ke tata letak akhir

Secara default, saat bertransisi di antara dua tata letak, ukuran tata letak akan dianimasikan antara status awal dan akhirnya. Ini mungkin perilaku yang tidak diinginkan saat menganimasikan konten seperti teks.

Contoh berikut mengilustrasikan teks deskripsi "Lorem Ipsum" yang masuk ke layar dengan dua cara yang berbeda. Contoh pertama teks berubah posisi/geometrinya saat ukuran penampung bertambah besar, contoh kedua teks tidak mengalir ulang seiring pertumbuhannya. Menambahkan Modifier.skipToLookaheadSize() akan mencegah perubahan posisi/geometri seiring pertumbuhannya.

No Modifier.skipToLookahead() - perhatikan bahwa teks "Lorem Ipsum" berubah posisi

Modifier.skipToLookahead() - perhatikan bahwa teks "Lorem Ipsum" mempertahankan status akhirnya di awal animasi

Klip dan overlay

Konsep penting saat membuat elemen bersama di Compose adalah agar elemen tersebut dapat berbagi di antara composable yang berbeda, rendering composable akan ditingkatkan menjadi overlay lapisan saat transisi dimulai dengan kecocokan di tujuan. Efeknya, fungsi ini akan lolos batas induk dan transformasi lapisannya (misalnya, alfa dan skala).

Elemen tersebut akan dirender di atas elemen UI non-bersama lainnya, setelah transisi selesai, elemen akan dihapus dari overlay ke DrawScope-nya sendiri.

Untuk memotong elemen bersama ke suatu bentuk, gunakan fungsi Modifier.clip() standar. Tempatkan setelah sharedElement():

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

Jika Anda perlu memastikan bahwa elemen bersama tidak pernah dirender di luar penampung induk, Anda dapat menetapkan clipInOverlayDuringTransition pada sharedElement(). Secara default, untuk batas bersama bertingkat, clipInOverlayDuringTransition menggunakan jalur klip dari sharedBounds() induk.

Untuk mendukung mempertahankan elemen UI tertentu, seperti panel bawah atau tombol tindakan mengambang, selalu berada di atas selama transisi elemen bersama, gunakan Modifier.renderInSharedTransitionScopeOverlay(). Secara default, pengubah ini mempertahankan konten dalam overlay selama transisi bersama aktif.

Misalnya, di Jetsnack, BottomAppBar harus ditempatkan di atas elemen bersama hingga layar tidak terlihat. Menambahkan pengubah ke composable akan membuatnya tetap tinggi.

Tanpa Modifier.renderInSharedTransitionScopeOverlay()

Dengan Modifier.renderInSharedTransitionScopeOverlay()

Terkadang, Anda mungkin ingin composable non-bersama dianimasikan serta tetap berada di atas composable lain sebelum transisi. Dalam kasus semacam ini, gunakan renderInSharedTransitionScopeOverlay().animateEnterExit() untuk menganimasikan composable saat transisi elemen bersama berjalan:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

Gambar 2.Panel Aplikasi Bawah yang bergeser masuk dan keluar saat transisi animasi

Dalam kasus yang jarang terjadi, jika Anda ingin elemen bersama tidak dirender dalam overlay, Anda dapat menetapkan renderInOverlayDuringTransition pada sharedElement() ke salah (false).

Memberi tahu tata letak seinduk tentang perubahan ukuran elemen bersama

Secara default, sharedBounds() dan sharedElement() tidak memberi tahu penampung induk tentang perubahan ukuran apa pun saat tata letak bertransisi.

Untuk menyebarkan perubahan ukuran ke penampung induk saat bertransisi, ubah parameter placeHolderSize menjadi PlaceHolderSize.animatedSize. Melakukan hal tersebut akan menyebabkan item bertambah atau menyusut. Semua item lain dalam tata letak akan merespons perubahan tersebut.

PlaceholderSize.contentSize (default)

PlaceholderSize.animatedSize

(Perhatikan bagaimana item lain dalam daftar bergerak ke bawah sebagai respons terhadap satu item yang bertambah)