Navigation avec des éléments partagés

Figure 1. Navigation avec des éléments partagés.

Les éléments partagés rendent les transitions entre les écrans plus fluides et plus attrayantes en créant une connexion visuelle qui guide l'utilisateur. Ce guide explique comment utiliser les API d'éléments partagés avec les bibliothèques Jetpack Navigation 3 et Navigation 2.

L'extrait de code suivant inclut les composables DetailsScreen et HomeScreen, qui servent de destinations entre lesquelles les utilisateurs peuvent naviguer. Dans chaque écran, le sharedElement modificateur est utilisé à la fois sur l'image et sur le texte, de sorte que chacun de ces éléments s'anime indépendamment entre les écrans.

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

Pour utiliser les API d'éléments partagés avec Navigation 3, vous devez d'abord encapsuler le NavDisplay de votre application dans un SharedTransitionLayout. Vous pouvez ensuite transmettre le fourni SharedTransitionScope aux composables d'écran.

Pour le AnimatedVisibilityScope, utilisez la LocalNavAnimatedContentScope composition locale qui fournit le AnimatedContentScope à partir du AnimatedContent que NavDisplay utilise en interne pour animer les scènes.

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

Pour utiliser les API d'éléments partagés avec Navigation 2, vous devez d'abord encapsuler le NavHost dans un SharedTransitionLayout. Vous pouvez ensuite transmettre le SharedTransitionScope fourni aux composables d'écran.

Le paramètre content du compilateur composable utilise AnimatedContentScope comme récepteur. Vous pouvez donc utiliser this@composable pour faire référence à cette portée.

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

Prévisualisation du Retour avec des éléments partagés

Pour utiliser la prévisualisation du Retour avec des éléments partagés, procédez comme suit :

  1. Toutes les versions de Navigation 3 sont compatibles avec la prévisualisation du Retour. Pour Navigation 2, utilisez la version 2.8.0-alpha02 de navigation-compose ou une version ultérieure :

    [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. Les animations de la prévisualisation du Retour sont activées par défaut sur les appareils équipés d'Android 15 (niveau d'API 35) ou version ultérieure. Pour les appareils équipés d'Android 14 (niveau d'API 34), vous devez activer le paramètre Prévisualisation du Retour dans les options pour les développeurs.

  3. Si votre application cible Android 14 ou une version antérieure, vous devez ajouter android:enableOnBackInvokedCallback="true" aux <application> ou éléments <activity> spécifiques de votre fichier AndroidManifest.xml. Cet indicateur n'est pas nécessaire si votre application cible Android 15 ou une version ultérieure.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <application
          ...
          android:enableOnBackInvokedCallback="true">
      </application>
    </manifest>
    
Figure 2. Éléments partagés avec prévisualisation du Retour.