Navigation
应用导航 · androidx.navigation
Navigation 3:自有 Back Stack2.10.0-beta01
用 NavKey、NavBackStack、entryProvider、NavDisplay 组合导航状态与显示策略。
最后更新 2026-08-01
最小模型
@Serializable object Home : NavKey
@Serializable data class Detail(val id: String) : NavKey
val backStack = rememberNavBackStack(Home)
NavDisplay(
backStack = backStack,
onBack = { backStack.removeAt(backStack.lastIndex) },
entryProvider = entryProvider {
entry<Home> {
HomeScreen { id -> backStack.add(Detail(id)) }
}
entry<Detail> { route ->
DetailScreen(route.id)
}
},
)核心思想:导航状态就是可观察的列表。
- 前进 =
backStack.add(navKey) - 返回 =
backStack.removeAt(lastIndex) NavDisplay要求 back stack 非空——根 entry 不能被普通返回删除。
NavKey 类型
@Serializable object Home : NavKey
@Serializable data class Profile(val id: String) : NavKey
@Serializable data class Settings(val tab: Int = 0) : NavKeyNavKey是导航目标的身份标识(类似 Nav2 的 route)。- 可以是
object(无参数)或data class(带参数)。 - 参数要小而可序列化(ID、枚举),完整对象由目标页自己拉取。
NavBackStack 操作
backStack.add(key) // 压栈(前进)
backStack.addAll(listOf(a, b)) // 批量压栈
backStack.removeAt(backStack.lastIndex) // 弹栈(返回)
backStack.removeLastOrNull() // 安全弹栈
backStack[0] = key // 替换栈底(如 tab 切换)| 场景 | 操作 |
|---|---|
| 普通前进 | add |
| 系统返回 | removeAt(lastIndex) |
| 底部导航切换 | 替换/重排(保留各 tab 的栈) |
| 回到根 | 移除到只剩根 |
Decorator:状态能力
NavDisplay(
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(), // 保存 rememberSaveable
rememberViewModelStoreNavEntryDecorator(), // 每 entry 独立 ViewModel
),
...
)| Decorator | 能力 |
|---|---|
rememberSaveableStateHolderNavEntryDecorator | entry 切换时保存/恢复 UI 状态 |
rememberViewModelStoreNavEntryDecorator | 每个 entry 独立 ViewModelStore(back stack 弹出才销毁) |
没有 ViewModel decorator:所有 entry 共享外层 ViewModelStore,返回后再进入会拿到旧 ViewModel。
NavDisplay 显示策略
NavDisplay 一次显示 back stack 的一个 entry(栈顶)。布局策略:
NavDisplay(
backStack = backStack,
onBack = ...,
entryProvider = ...,
modifier = Modifier.fillMaxSize(),
)- 单页面:直接
NavDisplay。 - 底部导航 + 内容:用
Scaffold+NavigationBar,NavDisplay 放内容区,tab 切换操作 backStack。 - 多栏(大屏):不同 window size 下把同一 backStack 渲染成不同布局。
与 Meme 项目的实际用法
// Meme 的 MainDisplay 模式:NavDisplay + 手动 backStack
val backStack = remember { mutableStateListOf<Any>(Main) }
NavDisplay(
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator(),
),
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
entryProvider = entryProvider {
entry<Main> { MainScreen() }
entry<Image> { ImageScreen(...) }
entry<Meme> { MemeScreen() }
},
)常见陷阱
- 根 entry 被 pop:back stack 空 → 崩溃或黑屏;onBack 要保护根。
- 忘 ViewModel decorator:entry 间 ViewModel 串扰。
- 参数塞大对象:不可序列化 → 崩溃;只传 ID。
- 手动管理不清理:无限 add 不 remove → 内存增长(用 removeAt 配合系统返回)。
复制即用
import androidx.navigation3.NavDisplay
import androidx.navigation3.NavKey
import androidx.navigation3.entryProvider
import androidx.navigation3.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.runtime.rememberViewModelStoreNavEntryDecorator
@Serializable object Home : NavKey
@Serializable data class Detail(val id: String) : NavKey
@Composable
fun AppNavigation() {
val backStack = rememberNavBackStack(Home)
NavDisplay(
backStack = backStack,
onBack = { if (backStack.size > 1) backStack.removeAt(backStack.lastIndex) },
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator(),
),
entryProvider = entryProvider {
entry<Home> {
HomeScreen(onOpen = { id -> backStack.add(Detail(id)) })
}
entry<Detail> { route ->
DetailScreen(route.id)
}
},
)
}要点
- Nav3 = NavKey 列表 + NavDisplay + entryProvider + decorator。
- 前进 add、返回 removeAt;根 entry 保护。
- ViewModel/saveable 状态靠 decorator;缺了会串扰或丢失。
- 应用拥有栈,NavDisplay 只负责显示栈顶。