共享元素通过创建引导用户的视觉连接,使屏幕之间的过渡更加流畅和引人入胜。本指南演示了如何 将共享元素 API 与 Navigation 3 和 Navigation 2 Jetpack 库一起使用。
以下代码段包含 DetailsScreen 和 HomeScreen 可组合项,它们充当用户可以在其间导航的目标。在每个屏幕中,
sharedElement 修饰符用于图片和文本,以便
每个元素在屏幕之间独立地进行动画处理。
@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, ) ) } } } } }
Navigation 3
如需将共享元素 API 与 Navigation 3 搭配使用,您必须先将应用的
NavDisplay封装在SharedTransitionLayout中。然后,您可以将提供的
SharedTransitionScope传递给屏幕可组合项。
对于 AnimatedVisibilityScope,请使用
LocalNavAnimatedContentScope本地组合,该组合提供
AnimatedContentScope中的 AnimatedContent,而 NavDisplay
在内部使用该组合在场景之间进行动画处理。
@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() }, ) } }) } }
Navigation 2
如需将共享元素 API 与 Navigation 2 搭配使用,您必须先将应用的
NavHost封装在SharedTransitionLayout中。然后,您可以将提供的 SharedTransitionScope 传递给屏幕可组合项。
content 形参使用
AnimatedContentScope 作为接收器,因此您可以使用 this@composable 引用该范围。composable
@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() } ) } } } }
使用共享元素的预测性返回
如需将 预测性返回 与共享元素搭配使用,请按以下步骤操作:
所有版本的 Navigation 3 都支持预测性返回。对于 Navigation 2,请使用
2.8.0-alpha02的navigation-compose版本或更高版本:[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) }在搭载 Android 15(API 级别 35)或更高版本的设备上,预测性返回动画默认处于启用状态。对于搭载 Android 14(API 级别 34)的设备, 您需要在开发者选项中启用“预测性返回”设置。
如果您的应用以 Android 14 或更低版本为目标平台,您必须将
android:enableOnBackInvokedCallback="true"添加到<application>或 特定<activity>元素中,这些元素位于您的AndroidManifest.xml文件。如果您的应用以 Android 15 或更高版本为目标平台,则不需要此标志。<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application ... android:enableOnBackInvokedCallback="true"> </application> </manifest>