Điều hướng bằng các phần tử dùng chung

Hình 1. Điều hướng bằng các phần tử dùng chung.

Các phần tử dùng chung giúp quá trình chuyển đổi giữa các màn hình trở nên mượt mà và hấp dẫn hơn bằng cách tạo ra một kết nối trực quan để hướng dẫn người dùng. Hướng dẫn này minh hoạ cách sử dụng API phần tử dùng chung với cả thư viện Jetpack Navigation 3 và Navigation 2.

Đoạn mã sau đây bao gồm các thành phần kết hợp DetailsScreen và HomeScreen đóng vai trò là đích đến mà người dùng có thể di chuyển giữa các đích đến. Trong mỗi màn hình, đối tượng sửa đổi sharedElement được dùng trên cả hình ảnh và văn bản để mỗi thành phần đó tạo ảnh động độc lập giữa các màn hình.

@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,
                            )
                    )
                }
            }
        }
    }
}

Để sử dụng API phần tử dùng chung với Navigation 3, trước tiên, bạn phải gói NavDisplay của ứng dụng trong một SharedTransitionLayout. Sau đó, bạn có thể truyền SharedTransitionScope được cung cấp đến các thành phần kết hợp màn hình.

Đối với AnimatedVisibilityScope, hãy sử dụng thành phần Compose LocalNavAnimatedContentScope cục bộ cung cấp AnimatedContentScope từ AnimatedContent mà NavDisplay sử dụng nội bộ để tạo ảnh động giữa các cảnh.

@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()
                        },
                    )
                }
            })
    }
}

Để sử dụng API phần tử dùng chung với Navigation 2, trước tiên, bạn phải gói NavHost của ứng dụng trong một SharedTransitionLayout. Sau đó, bạn có thể truyền SharedTransitionScope được cung cấp đến các thành phần kết hợp màn hình.

Tham số content của trình tạo composable sử dụng AnimatedContentScope làm đối tượng nhận, vì vậy, bạn có thể sử dụng this@composable để tham chiếu phạm vi đó.

@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()
                    }
                )
            }
        }
    }
}

Xem trước thao tác quay lại bằng các thành phần dùng chung

Để sử dụng tính năng xem trước thao tác quay lại với các phần tử dùng chung, hãy làm theo các bước sau:

  1. Tất cả các phiên bản của Navigation 3 đều hỗ trợ tính năng xem trước thao tác quay lại. Đối với Navigation 2, hãy sử dụng bản phát hành 2.8.0-alpha02 của navigation-compose trở lên:

    [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. Ảnh động xem trước thao tác quay lại được bật theo mặc định trên các thiết bị chạy Android 15 (cấp độ API 35) trở lên. Đối với các thiết bị chạy Android 14 (cấp độ API 34), bạn cần bật Chế độ cài đặt xem trước thao tác quay lại trong Tùy chọn cho nhà phát triển.

  3. Nếu ứng dụng của bạn nhắm đến Android 14 trở xuống, bạn phải thêm android:enableOnBackInvokedCallback="true" vào <application> hoặc các phần tử <activity> cụ thể trong tệp AndroidManifest.xml. Bạn không cần cờ này nếu ứng dụng của bạn nhắm đến Android 15 trở lên.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <application
          ...
          android:enableOnBackInvokedCallback="true">
      </application>
    </manifest>
    
Hình 2. Các thành phần dùng chung có tính năng xem trước thao tác quay lại.