デスティネーション間のアニメーション

NavDisplay には、ユーザーがアプリ内を移動する際にスムーズな 視覚的トランジションを作成するためのアニメーション機能が組み込まれています。これらの アニメーションは、メタデータを使用して NavDisplay 全体でカスタマイズすることも、Scene レベルでカスタマイズすることもできます。

組み込みのアニメーション機能について

NavDisplayContentTransform API を使用して、ナビゲーション中のコンテンツのアニメーション方法を定義します。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 は、すべてのエントリが常に 1 つのシーンにのみ表示されることを確認します。これにより、NavEntry をレンダリングするシーンが変更されると、トランジションが途切れることがあります。シーン間でエントリをスムーズにアニメーション化するには、次の例に示すように、NavDisplaySharedTransitionLayout でラップし、SharedTransitionScopeNavDisplay に指定します。

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

図 2. SharedTransitionScopeNavDisplay に渡さない場合のトランジションの途切れ。
図 3. SharedTransitionScopeNavDisplay に渡す場合のスムーズなトランジション。