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 gerakan ukuran dan posisi, Anda dapat menentukan parameter boundsTransform yang berbeda di Modifier.sharedElement(). Hal ini memberikan posisi Rect awal dan posisi Rect target.

Misalnya, untuk membuat teks dalam contoh sebelumnya agar bergerak dengan gerakan arc, 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 apa pun. 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 transisi elemen bersama di antara dua status. ScaleToBounds terlebih dahulu mengukur tata letak turunan dengan batasan Lookahead (atau Target). Kemudian, tata letak stabil turunan diskalakan agar sesuai dengan batas bersama. ScaleToBounds dapat dianggap sebagai "skala grafis" di antara status.

Sedangkan 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 menjadi setiap frame.

Untuk composable Text, ScaleToBounds direkomendasikan karena akan menghindari penataan ulang dan pengaliran ulang teks ke baris yang berbeda. Untuk batas yang memiliki rasio aspek yang berbeda, dan jika Anda menginginkan kontinuitas yang lancar antara dua elemen bersama, RemeasureToBounds direkomendasikan.

Perbedaan antara kedua mode ubah ukuran tersebut dapat dilihat pada contoh berikut:

ScaleToBounds

RemeasureToBounds

Lewati ke tata letak akhir

Secara default, saat melakukan transisi antara dua tata letak, ukuran tata letak akan menganimasikan antara status awal dan akhir. Hal ini mungkin merupakan perilaku yang tidak diinginkan saat mengoanimasi konten seperti teks.

Contoh berikut mengilustrasikan teks deskripsi "Lorem Ipsum" yang masuk ke layar dengan dua cara berbeda. Contoh pertama, teks ditata ulang saat memasuki saat ukuran penampung bertambah, contoh kedua, teks tidak ditata ulang saat bertambah. Menambahkan Modifier.skipToLookaheadSize() akan mencegah perataan ulang seiring pertumbuhannya.

Tidak ada Modifier.skipToLookahead() - perhatikan bahwa teks "Lorem Ipsum" berubah posisi/geometri

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

Klip dan overlay

Konsep penting saat membuat elemen bersama di Compose adalah agar elemen tersebut dapat dibagikan di antara composable yang berbeda, rendering composable akan ditingkatkan menjadi overlay lapisan saat transisi dimulai ke kecocokannya di tujuan. Efeknya adalah kode ini akan keluar dari batas induk dan transformasi lapisannya (misalnya alfa dan skala).

Elemen ini 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 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 perlu memastikan bahwa elemen bersama tidak pernah dirender di luar penampung induk, Anda dapat menetapkan clipInOverlayDuringTransition di sharedElement(). Secara default, untuk batas bersama bertingkat, clipInOverlayDuringTransition menggunakan jalur klip dari sharedBounds() induk.

Untuk mendukung penyimpanan 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 di 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 dan tetap berada di atas composable lain sebelum transisi. Dalam kasus tersebut, 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 sebagai transisi animasi

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

Memberi tahu tata letak saudara tentang perubahan pada ukuran elemen bersama

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

Untuk menerapkan perubahan ukuran ke penampung induk saat bertransisi, ubah parameter placeHolderSize menjadi PlaceHolderSize.animatedSize. Tindakan tersebut akan menyebabkan item membesar atau mengecil. Semua item lain dalam tata letak merespons perubahan.

PlaceholderSize.contentSize (default)

PlaceholderSize.animatedSize

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