NavController 是 Navigation Compose 库的核心组件,管理 Android 应用程序中的导航堆栈和 back stack 状态。通过 NavController 执行屏幕之间的切换、返回上一页面以及路由之间的数据传递。根据 Android Developers (2025),NavController 是任何具有多个屏幕的 Compose 应用程序的必需元素。控制器通过 rememberNavController() 创建,传递给 NavHost,可从组合中的任意点调用 navigate()。内置的 SavedStateHandle 支持在重新配置时自动保存 ViewModel 状态。
要点
NavController — 来自 Navigation Compose 库的一个类,为 Compose 应用程序实现导航控制器。NavController 管理 NavBackStackEntry 堆栈,其中每个条目包含路由、参数和屏幕状态。控制器支持基本导航操作:切换、返回、替换和清除。
与通过 FragmentManager 或 Intent 进行导航的 View 系统不同,NavController 仅在 Compose 上下文中工作。Back stack 存储为 NavDestination 图,而不是 Fragment 堆栈。这消除了创建和销毁 Fragment 的开销,并简化了测试——NavController 可以通过 TestNavHostController 进行模拟。
NavController 与 NavHost 紧密相关——从图中渲染当前屏幕的容器。没有 NavHost,NavController 无法显示 composable 函数,但保留管理堆栈的能力。在典型架构中,NavController 在 Activity 或主 composable 级别创建,并通过参数沿组合树向下传递。
根据 Google 的资料,NavController 经历了多个主要版本。2.8.0 版本添加了 Type-Safe Navigation,2.9.0 版本——支持 predictive back gesture(Android 14+)。该控制器与 Material3 Scaffold 和 BottomNavigation 兼容。对于多模块项目,NavController 通过 DI(Hilt/Koin)或构造函数参数传递。
NavController 通过 rememberNavController() composable 函数创建。该函数返回一个 NavHostController 实例(NavController 的子类),与当前 composable 的生命周期绑定。退出组合时,控制器被清除。要在重新配置时保存控制器,请使用 rememberSaveable 或 ViewModel。
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
composable("details") { DetailsScreen(navController) }
}
}
NavController 的配置包括:NavHostController(主要)、TestNavHostController(测试)和 ScopedNavController(用于嵌套图的子控制器)。对于 BottomNavigation,NavController 应对整个应用程序唯一——在每个选项卡中创建新控制器将导致堆栈丢失。要将控制器传递给嵌套屏幕,请使用函数参数而不是 CompositionLocalProvider,以保持可读性。
要测试导航,请使用带有 compose-test-rule 的 TestNavHostController。该控制器允许设置初始路由并验证 navigate() 是否调用了预期的切换。NavController 的测试不需要模拟器——它与 Compose Test 的 Semantics 匹配器一起工作。
navigate(route: String) 方法——NavController 中导航的主要方式。接受路由字符串、可选的 NavOptions 和 Navigator.Extras。NavOptions 管理切换行为:launchSingleTop(不在堆栈中重复路由)、popUpTo(清除堆栈到路由)、restoreState(恢复先前状态)。
NavOptions 通过构建器语法设置:NavOptionsBuilder。主要参数:popUpTo(route + inclusive/saveState)、launchSingleTop(Boolean,true——不创建重复项)、restoreState(返回时恢复状态)。没有 popUpTo,每个 navigate() 都会向堆栈添加条目,导致 back stack 累积和 Back 按钮行为不正确。
navController.navigate("profile/42") {
popUpTo("main") { saveState = true }
launchSingleTop = true
restoreState = true
}
Navigator.Extras 允许传递不属于路由的额外数据:用于动画的共享元素、Intent 标志、Pac-Man bundle。Extras 很少使用——主要用于与 Accompanist Animation 或自定义 Navigator 的集成。对于大多数场景,路由字符串和 NavOptions 就足够了。
popBackStack() — 返回上一屏幕的方法。无参数时删除堆栈顶部条目并返回 true(如果删除成功)。如果堆栈为空——该方法返回 false,Activity 关闭(类似于 super.onBackPressed())。
重载版本 popBackStack(route: String, inclusive: Boolean) 删除直到指定路由的所有条目。如果 inclusive = true——指定的路由本身也被删除。该方法返回 Boolean——如果找到并删除了条目则返回 true。带有 inclusive 的版本适用于授权或订单完成后“退出到主屏幕”的场景。
| 方法 | 描述 | 示例 |
|---|---|---|
| popBackStack() | 向后返回一个屏幕 | navController.popBackStack() |
| popBackStack(route, false) | 清除到路由(路由保留) | popBackStack("home", false) |
| popBackStack(route, true) | 清除到并包括路由 | popBackStack("home", true) |
| navigate(route) { popUpTo(route) { inclusive = true } } | 完全清除的切换 | navigate("login") { popUpTo(0) { inclusive = true } } |
要处理系统 Back 按钮(硬件返回按钮),请使用 Compose 中的 BackHandler。BackHandler 接受 enabled 和 onBack——按下时调用的回调。对于 Android 14+,使用 PredictiveBackGesture,通过 NavController 2.9.0 版本集成。Predictive back 添加了返回预览动画。
SavedStateHandle — 一种在导航和重新配置时保存 ViewModel 状态的机制。NavController 自动为每个 NavBackStackEntry 提供 SavedStateHandle。通过 SavedStateHandle,ViewModel 存储屏幕状态并在返回时恢复(restoreState = true)。
在 Navigation Compose 中,SavedStateHandle 与 ViewModel 一起使用:ViewModel 通过从 backStackEntry 传递的 SavedStateHandle 进行初始化。在切换到另一个屏幕并返回时(使用 restoreState),ViewModel 接收保存的状态,而不是重新创建。这对于具有数据输入、过滤器或滚动的屏幕至关重要。
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String = savedStateHandle.get<String>("userId") ?: ""
var searchQuery by savedStateHandle.getStateFlow("search", "")
.collectAsState()
}
SavedStateHandle 支持原始类型、String、Bundle 和 Parcelable。对于复杂对象,只保存 ID,完整数据从存储库加载。SavedStateHandle 的限制约为 1 MB,超出会导致 TransactionTooLargeException。对于大量数据,请使用 Room 或 DataStore 而不是在 handle 中保存。
重要提示:SavedStateHandle 仅在使用 NavOptions 中的 restoreState = true 时保存状态。如果未指定 restoreState,返回时 ViewModel 将使用默认值重新创建。对于使用 restoreState 切换 BottomNavigation,NavController 保存每个选项卡的状态并在重新选择时恢复。
currentBackStackEntryAsState() — 返回 State<NavBackStackEntry?> 的函数,该状态在当前路由每次更改时更新。这是将 UI 与导航同步的主要机制:BottomNavigation 突出显示活动元素,Toolbar 更新标题,Drawer 在切换时关闭。
该函数通过 snapshotFlow 和 collectAsState 工作:当 back stack 更改时,Compose 重新组合订阅的元素。重要提示:currentBackStackEntryAsState() 仅在切换动画完成后更新。要立即更新,请使用 currentDestination,它与 navigate() 同步更改,但不支持状态。
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Text(
text = when (currentRoute) {
"home" -> "Home"
"profile" -> "Profile"
else -> ""
}
)
要访问当前路由的参数,请使用 navBackStackEntry?.arguments。这在 BottomNavigation 中很方便:selectedItem 基于 currentRoute 计算。要调试导航,请使用 NavController.addOnDestinationChangedListener(),它会记录每次切换。在生产中,避免在大量 composable 中进行订阅——在 ViewModel 中创建单一源并将 State 传递给 UI。
常见问题
技术上可以,但不建议。单个 NavController 确保一致的 back stack 并简化调试。多个控制器仅对于具有独立导航的嵌套图是合理的(例如,具有自己堆栈的 modal bottom sheet)。
通过构造函数或 DI 将 NavController 传递给 ViewModel。但是,最好只传递回调函数(onNavigate、onBack),而不是 NavController 本身——这简化了测试。对于事件,在 ViewModel 中使用 Channel<NavEvent> 并在 UI 中收集。
问题在于生命周期:如果 NavController 尚未初始化(NavHost 尚未构建),navigate() 将被忽略。使用 LaunchedEffect 在数据加载后调用导航,而不是在具有任意生命周期协程内部。
调用 navController.navigate("target") { popUpTo(0) { inclusive = true } }。参数 popUpTo(0) 完全清除堆栈,inclusive = true 也删除初始条目。标志 launchSingleTop = true 防止新路由重复。
NavHostController — NavController 的子类,具有用于 NavHost 的额外方法(例如 setOnBackStackChangedListener)。NavController — 基类,可在 NavHost 之外用于程序化堆栈管理。在大多数情况下,使用 NavHostController。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。