Đ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 diễn ra mượt mà và hấp dẫn hơn bằng cách tạo mối liên kết trực quan hướng dẫn người dùng. Hướng dẫn này minh hoạ cách sử dụng các API phần tử dùng chung với cả thư viện Navigation 3Navigation 2 của Jetpack.

Đoạn mã sau đây bao gồm các thành phần kết hợp DetailsScreenHomeScreen đó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, bộ sửa đổi sharedElement được dùng cho cả hình ảnh và văn bản để mỗi phần tử trong số đó có thể độc lập tạo ảnh động 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 bao bọc NavDisplay của ứng dụng trong một SharedTransitionLayout. Sau đó, bạn có thể truyền SharedTransitionScope được cung cấp cho 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ừ AnimatedContentNavDisplay sử dụng nội bộ để tạo hiệu ứng chuyển độ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 bao bọc NavHost của ứng dụng trong một SharedTransitionLayout. Sau đó, bạn có thể truyền SharedTransitionScope đã 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 dùng AnimatedContentScope làm đối tượng nhận, nên bạn có thể 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 phần tử 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 hoặc phiên bản mới hơ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 những 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 các tuỳ 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 phần tử dùng chung có tính năng xem trước thao tác quay lại.