Навигация с использованием общих элементов

Рисунок 1. Навигация с использованием общих элементов.

Использование общих элементов делает переходы между экранами более плавными и привлекательными, создавая визуальную связь, которая направляет пользователя. В этом руководстве показано, как использовать API общих элементов с библиотеками Jetpack для Navigation 3 и Navigation 2 .

В приведенном ниже фрагменте кода представлены составные элементы 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,
                            )
                    )
                }
            }
        }
    }
}

Для использования API общих элементов с Navigation 3 необходимо сначала обернуть NavDisplay вашего приложения в SharedTransitionLayout . Затем вы можете передать предоставленный SharedTransitionScope в составные элементы экрана.

Для AnimatedVisibilityScope используйте локальную переменную композиции 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()
                        },
                    )
                }
            })
    }
}

Для использования API общих элементов с Navigation 2 необходимо сначала обернуть NavHost вашего приложения в SharedTransitionLayout . Затем вы можете передать предоставленный SharedTransitionScope в составные элементы экрана.

Параметр 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()
                    }
                )
            }
        }
    }
}

Прогнозируемый ответ с использованием общих элементов

Чтобы использовать функцию предиктивной отправки при работе с общими элементами, выполните следующие шаги:

  1. Все версии 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)
    }
    
  2. Анимация «Предсказательное нажатие кнопки назад» включена по умолчанию на устройствах под управлением Android 15 (уровень API 35) или выше. Для устройств под управлением Android 14 (уровень API 34) необходимо включить параметр «Предсказательное нажатие кнопки назад» в параметрах разработчика.

  3. Если ваше приложение ориентировано на 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>
    
Рисунок 2. Общие элементы с функцией предиктивной обратной связи.