在目的地之间添加动画效果

NavDisplay 提供内置动画功能,可在用户浏览应用时创建流畅的视觉过渡。您可以使用元数据在 NavDisplay 或 Scene 级别全局自定义这些动画。

了解内置动画功能

NavDisplay 使用 ContentTransform API 定义内容在导航期间如何呈现动画效果。当派生自当前场景的类及其 key 属性的键发生更改时,NavDisplay 会自动为场景之间的转换添加动画效果。当此键发生变化时,NavDisplay 会根据过渡类型(前进、后退或预测性返回)使用过渡中相应场景的 ContentTransform。如果未定义该 ContentTransform,NavDisplay 会回退到使用其对应的默认过渡。

覆盖默认转场效果

您可以通过向 NavDisplay 提供过渡参数来替换默认动画行为。

  • transitionSpec:此参数定义在将内容添加到返回堆栈时(即向前导航时)要应用的 ContentTransform。
  • popTransitionSpec:此参数定义了从返回堆栈中移除内容(即向后导航)时要应用的 ContentTransform。
  • predictivePopTransitionSpec:此参数定义了使用预测性返回手势弹出内容时要应用的 ContentTransform。

在Scene级别替换转场效果

您可以使用 metadata,通过 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 中,并为 NavDisplay 提供 SharedTransitionScope,如以下示例所示:

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

图 2. 未将 SharedTransitionScope 传递给 NavDisplay 时,过渡效果会跳跃。
图 3. 在将 SharedTransitionScope 传递给 NavDisplay 时实现平稳过渡。