רכיבים משותפים יוצרים קשר חזותי שמנחה את המשתמש, וכך הופכים את המעברים בין המסכים לחלקים יותר ומושכים יותר. במדריך הזה מוסבר איך להשתמש בממשקי ה-API של רכיבים משותפים עם ספריות Jetpack Navigation 3 ו-Navigation 2.
קטע הקוד הבא כולל את רכיבי ה-Composable 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, ) ) } } } } }
ניווט 3
כדי להשתמש בממשקי ה-API של הרכיבים המשותפים עם Navigation 3, צריך קודם לעטוף את NavDisplay של האפליקציה ב-SharedTransitionLayout. אחר כך אפשר להעביר את SharedTransitionScope שסופק לרכיבי ה-Composable של המסך.
בשביל AnimatedVisibilityScope, משתמשים ב-LocalNavAnimatedContentScope composition local שמספק את 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() }, ) } }) } }
ניווט 2
כדי להשתמש בממשקי ה-API של רכיבים משותפים עם Navigation 2, קודם צריך לעטוף את NavHost של האפליקציה ב-SharedTransitionLayout. אחר כך אפשר להעביר את SharedTransitionScope שסופק לרכיבי ה-Composable של המסך.
הפרמטר content של בונה composable משתמש ב-AnimatedContentScope כמקבל, כך שאפשר להשתמש ב-this@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 תומכות בתכונה 'חזרה חזויה'. בניווט 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>