[ Web Proxy ]
URL:
Viewing: http://developer.android.com/develop/ui/compose/animation/shared-elements/navigation [Back]  [Original]

Navigation with shared elements  |  Jetpack Compose  |  Android Developers Skip to main content
Essentials Design & Plan Develop Google Play Blog
Search:
Android Studio
Android Developers [Android Developers]

Navigation with shared elements Stay organized with collections Save and categorize content based on your preferences.

Figure 1. Navigation with shared elements.

Shared elements make transitions between screens smoother and more engaging by creating a visual connection that guides the user. This guide demonstrates how to use the shared element APIs with both the Navigation 3 and Navigation 2 Jetpack libraries.

The following snippet includes DetailsScreen and HomeScreen composables that serve as the destinations users can navigate between. Within each screen, the sharedElement modifier is used on both the image and the text so that each of those elements independently animates between screens.

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

To use the shared element APIs with Navigation 3, you must first wrap your app's NavDisplay in a SharedTransitionLayout. You can then pass the provided SharedTransitionScope to the screen composables.

For the AnimatedVisibilityScope, use the LocalNavAnimatedContentScope composition local that provides the AnimatedContentScope from the AnimatedContent that NavDisplay uses internally to animate between scenes.

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

To use the shared element APIs with Navigation 2, you must first wrap your app's NavHost in a SharedTransitionLayout. You can then pass the provided SharedTransitionScope to the screen composables.

The content parameter of the composable builder uses AnimatedContentScope as a receiver, so you can use this@composable to reference that scope.

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

Predictive back with shared elements

To use predictive back with shared elements, follow these steps:

  1. All versions of Navigation 3 support predictive back. For Navigation 2, use the 2.8.0-alpha02 release of navigation-compose or newer:

    [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. Predictive back animations are enabled by default on devices running Android 15 (API level 35) or higher. For devices running Android 14 (API level 34), you need to enable the Predictive back setting in developer options.

  3. If your app targets Android 14 or lower, you must add android:enableOnBackInvokedCallback="true" to the <application> or specific <activity> elements in your AndroidManifest.xml file. You don't need this flag if your app targets Android 15 or higher.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <application
          ...
          android:enableOnBackInvokedCallback="true">
      </application>
    </manifest>
    
Figure 2. Shared elements with predictive back.
Common use cases
Additional samples

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2026-08-14 UTC.

[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2026-08-14 UTC."],[],[]]

Web Proxy Viewer  |  New URL  |  Original Page