רכיבים משותפים הופכים את המעברים בין המסכים לחלקים ומושכים יותר, על ידי יצירת חיבור חזותי שמנחה את המשתמש. במדריך הזה מוסבר איך להשתמש בממשקי ה-API של הרכיבים המשותפים עם ספריות Jetpack של Navigation 3 ו-Navigation 2.
קטע הקוד הבא כולל את הפונקציות הקומפוזביליות DetailsScreen ו-HomeScreen, שמשמשות כיעדים שהמשתמשים יכולים לנווט ביניהם. בכל מסך, משתמשים ב-modifier 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, משתמשים ב-CompositionLocal של 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() }, ) } }) } }
ניווט 2
כדי להשתמש בממשקי ה-API של הרכיבים המשותפים עם Navigation 2, קודם צריך לעטוף את ה-NavHost של האפליקציה ב-SharedTransitionLayout. לאחר מכן, אפשר להעביר את ה-SharedTransitionScope שסופק לפונקציות ה-composable של המסך.
הפרמטר content של ה-builder 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 תומכות בחיזוי החזרה. ב-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>