Navigasi dengan elemen bersama

Gambar 1. Navigasi dengan elemen bersama.

Elemen bersama membuat transisi antar-layar lebih lancar dan lebih menarik dengan membuat koneksi visual yang memandu pengguna. Panduan ini menunjukkan cara menggunakan API elemen bersama dengan library Jetpack Navigation 3 dan Navigation 2.

Cuplikan berikut menyertakan composable DetailsScreen dan HomeScreen yang berfungsi sebagai tujuan yang dapat dibuka pengguna. Dalam setiap layar, pengubah sharedElement digunakan pada gambar dan teks sehingga setiap elemen tersebut menganimasikan secara independen di antara layar.

@Composable
fun DetailsScreen(
    id: Int,
    snack: Snack,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope,
    onBackPressed: () -> Unit
) {
    with(sharedTransitionScope) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .clickable { onBackPressed() },
        ) {
            Image(
                painterResource(id = snack.image),
                contentDescription = snack.description,
                contentScale = ContentScale.Crop,
                modifier = Modifier
                    .sharedElement(
                        sharedTransitionScope.rememberSharedContentState(key = "image-$id"),
                        animatedVisibilityScope = animatedVisibilityScope
                    )
                    .aspectRatio(1f)
                    .fillMaxWidth()
            )
            Text(
                text = snack.name,
                fontSize = 18.sp,
                modifier = Modifier
                    .sharedElement(
                        sharedTransitionScope.rememberSharedContentState(key = "text-$id"),
                        animatedVisibilityScope = animatedVisibilityScope
                    )
                    .fillMaxWidth(),
            )
        }
    }
}

@Composable
fun HomeScreen(
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope,
    onItemClick: (Int) -> Unit,
) {
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        itemsIndexed(listSnacks) { index, item ->
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .clickable { onItemClick(index) },
            ) {
                Spacer(modifier = Modifier.width(8.dp))
                with(sharedTransitionScope) {
                    Image(
                        painterResource(id = item.image),
                        contentDescription = item.description,
                        contentScale = ContentScale.Crop,
                        modifier = Modifier
                            .sharedElement(
                                sharedTransitionScope.rememberSharedContentState(key = "image-$index"),
                                animatedVisibilityScope = animatedVisibilityScope
                            )
                            .size(100.dp)
                    )
                    Spacer(modifier = Modifier.width(8.dp))
                    Text(
                        item.name,
                        fontSize = 18.sp,
                        modifier = Modifier
                            .align(Alignment.CenterVertically)
                            .sharedElement(
                                sharedTransitionScope.rememberSharedContentState(key = "text-$index"),
                                animatedVisibilityScope = animatedVisibilityScope,
                            )
                    )
                }
            }
        }
    }
}

Untuk menggunakan API elemen bersama dengan Navigation 3, Anda harus menggabungkan NavDisplay aplikasi dalam SharedTransitionLayout terlebih dahulu. Kemudian, Anda dapat meneruskan SharedTransitionScope yang disediakan ke composable layar.

Untuk AnimatedVisibilityScope, gunakan komposisi lokal LocalNavAnimatedContentScope yang menyediakan AnimatedContentScope dari AnimatedContent yang digunakan NavDisplay secara internal untuk menganimasikan antar-scene.

@Composable
fun SharedElement_Nav3() {
    SharedTransitionLayout {
        val backStack = rememberNavBackStack(HomeRoute)

        // Note: NavDisplay accepts a `sharedTransitionScope` parameter, which is used to animate
        // NavEntry instances between scenes. This parameter *isn't* required for shared element
        // or shared bounds transitioning elements between different NavEntry, as demonstrated in
        // this sample.
        // See https://developer.android.com/guide/navigation/navigation-3/animate-destinations#transition-nav-entries
        NavDisplay(
            modifier = Modifier.safeDrawingPadding(),
            backStack = backStack,
            entryProvider = entryProvider {
                entry<HomeRoute> {
                    HomeScreen(
                        sharedTransitionScope = this@SharedTransitionLayout,
                        animatedVisibilityScope = LocalNavAnimatedContentScope.current,
                        onItemClick = { backStack.add(DetailsRoute(it)) })
                }
                entry<DetailsRoute> { detailsRoute ->
                    val id = detailsRoute.item
                    val snack = listSnacks[id]

                    DetailsScreen(
                        id = id,
                        snack = snack,
                        sharedTransitionScope = this@SharedTransitionLayout,
                        animatedVisibilityScope = LocalNavAnimatedContentScope.current,
                        onBackPressed = {
                            backStack.removeLastOrNull()
                        },
                    )
                }
            })
    }
}

Untuk menggunakan API elemen bersama dengan Navigation 2, Anda harus menggabungkan NavHost aplikasi dalam SharedTransitionLayout terlebih dahulu. Kemudian, Anda dapat meneruskan SharedTransitionScope yang disediakan ke composable layar.

Parameter content dari builder composable menggunakan AnimatedContentScope sebagai penerima, sehingga Anda dapat menggunakan this@composable untuk mereferensikan cakupan tersebut.

@Composable
fun SharedElement_Nav2() {
    SharedTransitionLayout {
        val navController = rememberNavController()
        NavHost(
            navController = navController,
            startDestination = "home",
            modifier = Modifier.safeDrawingPadding()
        ) {
            composable("home") {
                HomeScreen(
                    sharedTransitionScope = this@SharedTransitionLayout,
                    animatedVisibilityScope = this@composable,
                    onItemClick = { navController.navigate("details/$it") })
            }
            composable(
                "details/{item}", arguments = listOf(navArgument("item") { type = NavType.IntType })
            ) { backStackEntry ->
                val id = backStackEntry.arguments?.getInt("item") ?: 0
                val snack = listSnacks[id]
                DetailsScreen(
                    id = id,
                    snack = snack,
                    sharedTransitionScope = this@SharedTransitionLayout,
                    animatedVisibilityScope = this@composable,
                    onBackPressed = {
                        navController.popBackStack()
                    }
                )
            }
        }
    }
}

Kembali prediktif dengan elemen bersama

Untuk menggunakan kembali prediktif dengan elemen bersama, ikuti langkah-langkah berikut:

  1. Semua versi Navigation 3 mendukung kembali prediktif. Untuk Navigation 2, gunakan rilis 2.8.0-alpha02 dari navigation-compose atau yang lebih baru:

    [versions]
    androidx-navigation = "2.8.0-alpha02" # Or newer
    
    [libraries]
    androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
    
    dependencies {
        implementation(libs.androidx.navigation.compose)
    }
    
  2. Animasi kembali prediktif diaktifkan secara default di perangkat yang menjalankan Android 15 (level API 35) atau yang lebih tinggi. Untuk perangkat yang menjalankan Android 14 (level API 34), Anda harus mengaktifkan Setelan kembali prediktif di opsi developer.

  3. Jika aplikasi Anda menargetkan Android 14 atau yang lebih rendah, Anda harus menambahkan android:enableOnBackInvokedCallback="true" ke <application> atau elemen <activity> tertentu dalam file AndroidManifest.xml. Anda tidak memerlukan flag ini jika aplikasi Anda menargetkan Android 15 atau yang lebih tinggi.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <application
          ...
          android:enableOnBackInvokedCallback="true">
      </application>
    </manifest>
    
Gambar 2. Elemen bersama dengan kembali prediktif.