Анимация перехода между пунктами назначения

NavDisplay предоставляет встроенные возможности анимации для создания плавных визуальных переходов при навигации пользователей в приложении. Вы можете настроить анимацию глобально для NavDisplay или на уровне Scene с помощью метаданных.

Встроенные возможности анимации

NavDisplay использует API ContentTransform, чтобы определить, как контент анимируется во время навигации. NavDisplay автоматически анимирует переходы между сценами, когда меняется ключ, полученный из класса текущей сцены и ее свойства key. Когда этот ключ меняется, NavDisplay использует ContentTransform для типа перехода (вперед, назад или с прогнозированием назад) из подходящей сцены в переходе. Если ContentTransform не определен, NavDisplay использует соответствующий переход по умолчанию.

Как переопределить переходы по умолчанию

Поведение анимации по умолчанию можно переопределить, передав параметры перехода в NavDisplay.

  • transitionSpec – параметр, определяющий ContentTransform, которое будет применено при добавлении контента в обратный стек (то есть при переходе вперед).
  • popTransitionSpec – параметр, который определяет ContentTransform, применяемый при удалении контента из стека возврата (то есть при переходе назад).
  • predictivePopTransitionSpec: этот параметр определяет ContentTransform, которое будет применено при показе контента с помощью жеста "Назад".

Как переопределить переходы на уровне Scene

Вы можете использовать метаданные, чтобы задавать собственные анимации для отдельных сцен, используя следующие ключи метаданных, определенные NavDisplay:

Если они указаны, то используются вместо настроек по умолчанию, заданных на уровне NavDisplay.

В приведенном ниже фрагменте кода показаны как глобальные переходы NavDisplay, так и переопределение на уровне отдельного элемента NavEntry:

@Serializable
data object ScreenA : NavKey

@Serializable
data object ScreenB : NavKey

@Serializable
data object ScreenC : NavKey

class AnimatedNavDisplayActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {

            Scaffold { paddingValues ->

                val backStack = rememberNavBackStack(ScreenA)

                NavDisplay(
                    backStack = backStack,
                    onBack = { backStack.removeLastOrNull() },
                    entryProvider = entryProvider {
                        entry<ScreenA> {
                            ContentOrange("This is Screen A") {
                                Button(onClick = { backStack.add(ScreenB) }) {
                                    Text("Go to Screen B")
                                }
                            }
                        }
                        entry<ScreenB> {
                            ContentMauve("This is Screen B") {
                                Button(onClick = { backStack.add(ScreenC) }) {
                                    Text("Go to Screen C")
                                }
                            }
                        }
                        entry<ScreenC>(
                            metadata = metadata {
                                put(NavDisplay.TransitionKey) {
                                    // Slide new content up, keeping the old content in place underneath
                                    slideInVertically(
                                        initialOffsetY = { it },
                                        animationSpec = tween(1000)
                                    ) togetherWith ExitTransition.KeepUntilTransitionsFinished
                                }
                                put(NavDisplay.PopTransitionKey) {
                                    // Slide old content down, revealing the new content in place underneath
                                    EnterTransition.None togetherWith
                                            slideOutVertically(
                                                targetOffsetY = { it },
                                                animationSpec = tween(1000)
                                            )
                                }
                                put(NavDisplay.PredictivePopTransitionKey) {
                                    // Slide old content down, revealing the new content in place underneath
                                    EnterTransition.None togetherWith
                                            slideOutVertically(
                                                targetOffsetY = { it },
                                                animationSpec = tween(1000)
                                            )
                                }
                            }
                        ) {
                            ContentGreen("This is Screen C")
                        }
                    },
                    transitionSpec = {
                        // Slide in from right when navigating forward
                        slideInHorizontally(initialOffsetX = { it }) togetherWith
                            slideOutHorizontally(targetOffsetX = { -it })
                    },
                    popTransitionSpec = {
                        // Slide in from left when navigating back
                        slideInHorizontally(initialOffsetX = { -it }) togetherWith
                            slideOutHorizontally(targetOffsetX = { it })
                    },
                    predictivePopTransitionSpec = {
                        // Slide in from left when navigating back
                        slideInHorizontally(initialOffsetX = { -it }) togetherWith
                            slideOutHorizontally(targetOffsetX = { it })
                    },
                    modifier = Modifier.padding(paddingValues)
                )
            }
        }
    }
}

Рисунок 1. Приложение с пользовательскими анимациями.

Переход между сценами

В приложениях, которые создают макеты с помощью сцен, во время перехода между сценами объект NavEntry может быть включен в свойство entries обеих сцен. Внутренние механизмы NavDisplay проверяют, чтобы каждый объект отображался не более чем в одной сцене в любой момент времени. Это может приводить к резким переходам при смене сцены, в которой отрисовывается NavEntry. Чтобы обеспечить плавный переход между сценами, вы можете обернуть NavDisplay в SharedTransitionLayout и передать SharedTransitionScope в NavDisplay, как показано в следующем примере:

SharedTransitionLayout {
    NavDisplay(
        // ...
        sharedTransitionScope = this
    )
}

Рисунок 2. Резкий переход, если не передавать SharedTransitionScope в NavDisplay.
Рисунок 3. Плавный переход от SharedTransitionScope к NavDisplay.