了解并实现基本功能

导航描述了用户在应用中移动的方式。用户通常通过点按或点击界面元素与其互动,而应用会通过显示新内容来做出响应。如果用户想要返回上一个内容,可以使用返回手势操作或点按返回按钮。

对导航状态进行建模

对这种行为进行建模的一种便捷方式是使用内容堆栈。当用户向前导航到新内容时,新内容会被推送到堆栈顶部。当用户从该内容中返回时,该内容会从堆栈中弹出,并显示之前的内容。在导航方面,此堆栈通常称为“返回堆栈”,因为它表示用户可以返回的内容。

以红色圆圈突出显示的软件键盘操作按钮(对勾图标)。
图 1. 显示后进栈如何随用户导航事件而变化的图表。

创建返回堆栈

在 Navigation 3 中,返回堆栈实际上不包含内容。相反,它包含对内容(称为“键”)的引用。键可以是任何类型,但通常是简单的可序列化数据类。使用引用而非内容具有以下优势:

  • 只需将键推送到返回堆栈,即可轻松导航。
  • 只要键可序列化,就可以将返回堆栈保存到持久性存储空间,从而使其在配置更改和进程终止后继续存在。这一点很重要,因为用户希望能够离开您的应用,稍后再返回该应用,并从上次离开时显示的同一内容继续使用。如需了解详情,请参阅保存返回堆栈

Navigation 3 API 中的一个关键概念是,您拥有返回堆栈。该库:

  • 预期您的返回栈将是受快照状态支持的 List<T>,其中 T 是返回栈 keys 的类型。您可以使用 Any,也可以提供自己的更强类型的键。当您看到“推送”或“弹出”这两个术语时,其底层实现是从列表末尾添加或移除项。
  • 观察您的返回堆栈,并使用 NavDisplay 在界面中反映其状态。

以下示例展示了如何创建键和返回堆栈,以及如何响应用户导航事件来修改返回堆栈:

// Define keys that will identify content
data object ProductList
data class ProductDetail(val id: String)

@Composable
fun MyApp() {

    // Create a back stack, specifying the key the app should start with
    val backStack = remember { mutableStateListOf<Any>(ProductList) }

    // Supply your back stack to a NavDisplay so it can reflect changes in the UI
    // ...more on this below...

    // Push a key onto the back stack (navigate forward), the navigation library will reflect the change in state
    backStack.add(ProductDetail(id = "ABC"))

    // Pop a key off the back stack (navigate back), the navigation library will reflect the change in state
    backStack.removeLastOrNull()
}

解析内容密钥

在 Navigation 3 中,内容使用 NavEntry 进行建模,这是一个包含可组合函数的类。它表示一个目的地,即用户可以向前向后导航到的单个内容片段。

NavEntry 还可以包含元数据(有关内容的信息)。容器对象(例如 NavDisplay)可以读取此元数据,以帮助它们决定如何显示 NavEntry 的内容。例如,元数据可用于替换特定 NavEntry 的默认动画。NavEntry metadata 是一个从 String 键到 Any 值的映射,可提供多用途的数据存储。

如需将 key 转换为 NavEntry,请创建条目提供程序。这是一个接受 key 并返回相应 keyNavEntry 的函数。在创建 NavDisplay 时,它通常定义为 lambda 参数。

您可以通过以下两种方式创建 Entry Provider:直接创建 lambda 函数,或使用 entryProvider DSL。

直接创建 Entry Provider 函数

您通常使用 when 语句创建一个 Entry Provider 函数,并为每个键创建一个分支。

entryProvider = { key ->
    when (key) {
        is ProductList -> NavEntry(key) { Text("Product List") }
        is ProductDetail -> NavEntry(
            key,
            metadata = mapOf("extraDataKey" to "extraDataValue")
        ) { Text("Product ${key.id} ") }

        else -> {
            NavEntry(Unit) { Text(text = "Invalid Key: $it") }
        }
    }
}

使用 entryProvider DSL

entryProvider DSL 可简化您的 lambda 函数,避免需要针对每种键类型进行测试,并为每种键类型构建一个 NavEntry。为此,请使用 entryProvider 构建器函数。如果找不到键,它还会包含默认回退行为(抛出错误)。

entryProvider = entryProvider {
    entry<ProductList> { Text("Product List") }
    entry<ProductDetail>(
        metadata = mapOf("extraDataKey" to "extraDataValue")
    ) { key -> Text("Product ${key.id} ") }
}

请注意代码段中的以下内容:

  • entry 用于定义具有给定类型和可组合内容的 NavEntry
  • entry 接受 metadata 参数来设置 NavEntry.metadata

显示返回堆栈

返回堆栈表示应用的导航状态。每当返回堆栈发生变化时,应用界面都应反映新的返回堆栈状态。在 Navigation 3 中,NavDisplay 会观察您的返回堆栈并相应地更新其界面。使用以下参数构造它:

  • 您的返回堆栈 - 这应为 SnapshotStateList<T> 类型,其中 T 是返回堆栈键的类型。它是可观测的 List,因此当它发生变化时,会触发 NavDisplay 的重组。
  • 用于将返回堆栈中的键转换为 NavEntry 对象的 entryProvider
  • (可选)为 onBack 参数提供 lambda。当用户触发返回事件时,系统会调用此方法。

以下示例展示了如何创建 NavDisplay

data object Home
data class Product(val id: String)

@Composable
fun NavExample() {

    val backStack = remember { mutableStateListOf<Any>(Home) }

    NavDisplay(
        backStack = backStack,
        onBack = { backStack.removeLastOrNull() },
        entryProvider = { key ->
            when (key) {
                is Home -> NavEntry(key) {
                    ContentGreen("Welcome to Nav3") {
                        Button(onClick = {
                            backStack.add(Product("123"))
                        }) {
                            Text("Click to navigate")
                        }
                    }
                }

                is Product -> NavEntry(key) {
                    ContentBlue("Product ${key.id} ")
                }

                else -> NavEntry(Unit) { Text("Unknown route") }
            }
        }
    )
}

默认情况下,NavDisplay 会在单窗格布局中显示返回堆栈中最顶层的 NavEntry。以下录制内容展示了此应用的运行情况:

具有两个目的地的 `NavDisplay` 默认行为。
图 2. NavDisplay 具有两个目的地的默认行为。

总结

下图显示了 Navigation 3 中各个对象之间的数据流:

直观呈现了 Navigation 3 中各个对象之间的数据流。
图 3. 图表:显示了数据如何在 Navigation 3 中的各个对象之间流动。
  1. 导航事件会触发更改。密钥会根据用户互动添加到返回堆栈或从返回堆栈中移除。

  2. 后退堆栈状态发生变化时,触发内容检索NavDisplay(用于呈现返回堆栈的可组合项)会观察返回堆栈。在默认配置中,它会在单窗格布局中显示最顶层的返回堆栈条目。当返回栈中的顶部键发生变化时,NavDisplay 会使用此键从条目提供程序请求相应的内容。

  3. 条目提供方提供内容。条目提供程序是一种将键解析为 NavEntry 的函数。在从 NavDisplay 收到密钥后,条目提供程序会提供关联的 NavEntry,其中包含密钥和内容。

  4. 内容显示NavDisplay 接收 NavEntry 并显示内容。