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

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.