Navigasi dengan elemen bersama

Gambar 1. Navigasi dengan elemen bersama.

Elemen bersama membuat transisi antar-layar lebih lancar dan 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 dinavigasi pengguna. Dalam setiap layar, pengubah sharedElement digunakan pada gambar dan teks sehingga setiap elemen tersebut secara independen dianimasikan 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 terlebih dahulu menggabungkan aplikasi Anda NavDisplay dalam SharedTransitionLayout. Kemudian, Anda dapat meneruskan yang disediakan SharedTransitionScope ke composable layar.

Untuk AnimatedVisibilityScope, gunakan LocalNavAnimatedContentScope komposisi lokal yang menyediakan AnimatedContentScope dari AnimatedContent yang NavDisplay digunakan 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 terlebih dahulu menggabungkan aplikasi Anda NavHost dalam SharedTransitionLayout. 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.