Navigasi dengan elemen bersama

Gambar 1. Navigasi dengan elemen bersama.

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

Cuplikan berikut mencakup composable DetailsScreen dan HomeScreen yang berfungsi sebagai tujuan yang dapat dinavigasi pengguna. Dalam setiap layar, pengubah sharedElement digunakan pada gambar dan teks sehingga setiap elemen tersebut dapat dianimasikan secara independen antar-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 membungkus NavDisplay aplikasi dalam SharedTransitionLayout terlebih dahulu. Kemudian, Anda dapat meneruskan SharedTransitionScope yang diberikan ke composable layar.

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

@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 membungkus NavHost aplikasi dalam SharedTransitionLayout terlebih dahulu. Kemudian, Anda dapat meneruskan SharedTransitionScope yang diberikan ke composable layar.

Parameter content 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 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 elemen <application> atau <activity> tertentu dalam file AndroidManifest.xml. Anda tidak memerlukan tanda 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.