Navigation mit gemeinsam genutzten Elementen

Abbildung 1. Navigation mit gemeinsamen Elementen

Gemeinsame Elemente sorgen für flüssigere und ansprechendere Übergänge zwischen Bildschirmen, da sie eine visuelle Verbindung schaffen, die den Nutzer leitet. In diesem Leitfaden wird gezeigt, wie Sie die APIs für gemeinsame Elemente sowohl mit den Jetpack-Bibliotheken Navigation 3 als auch Navigation 2 verwenden.

Das folgende Snippet enthält die Composables DetailsScreen und HomeScreen, die als Ziele dienen, zwischen denen Nutzer navigieren können. Auf jedem Bildschirm wird der Modifikator sharedElement sowohl für das Bild als auch für den Text verwendet, damit jedes dieser Elemente unabhängig voneinander zwischen den Bildschirmen animiert wird.

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

Wenn Sie die APIs für gemeinsame Elemente mit Navigation 3 verwenden möchten, müssen Sie zuerst die NavDisplay Ihrer App in ein SharedTransitionLayout einfügen. Anschließend können Sie das bereitgestellte SharedTransitionScope an die komponierbaren Funktionen des Bildschirms übergeben.

Verwenden Sie für AnimatedVisibilityScope die lokale Komposition LocalNavAnimatedContentScope, die die AnimatedContentScope aus dem AnimatedContent bereitstellt, das NavDisplay intern verwendet, um zwischen Szenen zu animieren.

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

Wenn Sie die APIs für gemeinsame Elemente mit Navigation 2 verwenden möchten, müssen Sie zuerst das NavHost Ihrer App in ein SharedTransitionLayout einfügen. Anschließend können Sie das bereitgestellte SharedTransitionScope an die Screen-Composables übergeben.

Der Parameter content des Builders composable verwendet AnimatedContentScope als Empfänger. Sie können also mit this@composable auf diesen Bereich verweisen.

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

Intelligente „Zurück“-Geste mit gemeinsamen Elementen

So verwenden Sie intelligente „Zurück“-Geste mit freigegebenen Elementen:

  1. Alle Versionen von Navigation 3 unterstützen die intelligente „Zurück“-Geste. Verwenden Sie für Navigation 2 die 2.8.0-alpha02-Version von navigation-compose oder höher:

    [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. Die intelligente „Zurück“-Geste ist auf Geräten mit Android 15 (API‑Level 35) oder höher standardmäßig aktiviert. Auf Geräten mit Android 14 (API-Level 34) müssen Sie die Einstellung für die Vorhersage der Zurück-Geste in den Entwickleroptionen aktivieren.

  3. Wenn Ihre App auf Android 14 oder niedriger ausgerichtet ist, müssen Sie android:enableOnBackInvokedCallback="true" dem Element <application> oder bestimmten <activity>-Elementen in Ihrer AndroidManifest.xml-Datei hinzufügen. Dieses Flag ist nicht erforderlich, wenn Ihre App auf Android 15 oder höher ausgerichtet ist.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <application
          ...
          android:enableOnBackInvokedCallback="true">
      </application>
    </manifest>
    
Abbildung 2. Freigegebene Elemente mit der intelligenten „Zurück“-Geste