简单的页面跳转
在 Compose 中,我们可以借助 State 实现一个非常简单的屏幕内容切换效果。
1class MainActivity : ComponentActivity() { 2 override fun onCreate(savedInstanceState: Bundle?) { 3 super.onCreate(savedInstanceState) 4 enableEdgeToEdge() 5 setContent { 6 ComposeNavigationTestTheme { 7 MyApp() 8 } 9 } 10 } 11} 12 13@Composable 14fun MyApp(modifier: Modifier = Modifier) { 15 // 当前显示的页面 16 var currentScreen by remember { mutableIntStateOf(0) } 17 18 when (currentScreen) { 19 0 -> ScreenA(modifier = modifier, onClick = { 20 currentScreen = 1 21 }) 22 23 1 -> ScreenB(modifier = modifier, onClick = { 24 currentScreen = 2 25 }) 26 27 2 -> ScreenC(modifier = modifier, onClick = { 28 currentScreen = 0 29 }) 30 } 31} 32 33@Composable 34fun ScreenA(modifier: Modifier = Modifier, onClick: () -> Unit) { 35 Column( 36 modifier = modifier.fillMaxSize(), 37 horizontalAlignment = Alignment.CenterHorizontally, 38 verticalArrangement = Arrangement.Center 39 ) { 40 Text("这里是页面 A") 41 Button(onClick = onClick) { 42 Text("跳转到页面 B") 43 } 44 } 45} 46 47@Composable 48fun ScreenB(modifier: Modifier = Modifier, onClick: () -> Unit) { 49 Column( 50 modifier = modifier.fillMaxSize(), 51 horizontalAlignment = Alignment.CenterHorizontally, 52 verticalArrangement = Arrangement.Center 53 ) { 54 Text("这里是页面 B") 55 Button(onClick = onClick) { 56 Text("跳转到页面 C") 57 } 58 } 59} 60 61@Composable 62fun ScreenC(modifier: Modifier = Modifier, onClick: () -> Unit) { 63 Column( 64 modifier = modifier.fillMaxSize(), 65 horizontalAlignment = Alignment.CenterHorizontally, 66 verticalArrangement = Arrangement.Center 67 ) { 68 Text("这里是页面 C") 69 Button(onClick = onClick) { 70 Text("跳转到页面 A") 71 } 72 } 73} 74
运行效果:
但当前的效果只有页面切换,没有页面导航。按下返回键时,只会回到桌面,无法回到上一个页面。
当然,你可以自己来实现导航功能,但我们更多会使用 Navigation 组件。
添加依赖
在 build.gradle.kts 文件中添加依赖:
1dependencies { 2 implementation("androidx.navigation:navigation-compose:2.9.6") 3} 4
简单用法
修改 MyApp 函数:
1const val SCREEN_A = "screen_a" 2const val SCREEN_B = "screen_b" 3const val SCREEN_C = "screen_c" 4 5@Composable 6fun MyApp(modifier: Modifier = Modifier) { 7 val navController = rememberNavController() 8 9 NavHost(navController = navController, startDestination = SCREEN_A, modifier = modifier) { 10 // 路由字符串映射到Composable页面 11 composable(route = SCREEN_A) { 12 ScreenA { 13 // 跳转 14 navController.navigate(route = SCREEN_B) 15 } 16 } 17 composable(route = SCREEN_B) { 18 ScreenB { 19 navController.navigate(route = SCREEN_C) 20 } 21 } 22 composable(route = SCREEN_C) { 23 ScreenC { 24 navController.navigate(route = SCREEN_A) 25 } 26 } 27 } 28} 29
我们定义了每个页面的路由,设置起始路由为 SCREEN_A,也就是启动页为 ScreenA。
这样就具备导航功能了,运行效果:
当前所在页面
要知道当前所在页面,可以使用 NavDestination 获取当前页面的路由。
1val navController = rememberNavController() 2navController.currentDestination?.route 3
在 Compose 中,我们更多会使用 NavController.currentBackStackEntryAsState(),因为它返回的是一个 State。
1val navController = rememberNavController() 2 3val currentBackStack by navController.currentBackStackEntryAsState() 4currentBackStack?.destination?.route 5
启动模式
跳转的默认行为对应的是 Activity 中的 Standard 启动模式。
我们还可以在跳转时指定额外的行为。
popUpTo
1// 假设在 ScreenC 中跳转到 ScreenA 2navController.navigate(route = SCREEN_A) { 3 popUpTo(route = SCREEN_A) { 4 inclusive = true 5 } 6} 7
上面的代码表示在跳转到 ScreenA 之前,会不断让页面出栈,直到遇到传入 popUpTo() 函数的路由参数对应的页面(ScreenA),将 inclusive(包括)属性设为 true,那么在遇到目标页面时,也会将目标页面弹出栈。
这个组合常用于模拟 Activity 的 singleTask 启动模式的栈行为。
比如当前的返回栈是 A -> B -> C,如果从 C 跳回 A,我们希望清除掉 A 上面的 B 和 C,并且不希望有两个 A(销毁旧的 A),就可以使用它。
但请注意,这和 Activity 的
singleTask行为并不完全一致,具体可以看我的这篇博客:精通 Activity 四大启动模式。
launchSingleTop
launchSingleTop 属性的效果类似于 Activity 的 singleTop 启动模式。
1composable(route = SCREEN_A) { 2 ScreenA { 3 navController.navigate(route = SCREEN_A) { 4 launchSingleTop = true 5 } 6 } 7} 8
当指定 launchSingleTop 为 true 时,NavController 会判断返回栈的顶部是否为导航目标页。如果是,NavController 就不会创建新的实例,而是会复用栈顶的实例。
避免了在栈顶多次创建相同的页面实例。
跳转时传递参数
必选参数
首先,修改 ScreenB,让它能够接收字符串参数 userId。
1@Composable 2fun ScreenB(modifier: Modifier = Modifier, userId: String? = null, onClick: () -> Unit = {}) { 3 Column( 4 modifier = modifier.fillMaxSize(), 5 horizontalAlignment = Alignment.CenterHorizontally, 6 verticalArrangement = Arrangement.Center 7 ) { 8 Text("这里是页面 B") 9 Text(text = "用户id为:$userId") 10 Button(onClick = onClick) { 11 Text("跳转到页面 C") 12 } 13 } 14} 15
接着修改 NavHost,使得路由节点能够支持参数传递。
1composable( 2 // 新增userId路由参数 3 route = "${SCREEN_B}/{userId}", 4 arguments = listOf( 5 // 指定路由参数的类型 6 navArgument(name = "userId") { 7 type = NavType.StringType 8 } 9 ) 10) { navBackStackEntry -> 11 // 获取参数数据 12 val userId = navBackStackEntry.arguments?.getString("userId") 13 ScreenB(userId = userId) { 14 navController.navigate(route = SCREEN_C) 15 } 16} 17
最后,在页面跳转时,将参数传递进来。
1composable(route = SCREEN_A) { 2 ScreenA { 3 val userId = "123" 4 navController.navigate(route = "${SCREEN_B}/$userId") 5 } 6} 7
运行效果:
可选参数
Navigation 也支持可选参数,类似于 URL 的查询参数。
修改 NavHost 中 ScreenB 的路由定义:
1composable( 2 // 定义可选参数 3 route = "${SCREEN_B}?userId={userId}", 4 arguments = listOf( 5 navArgument(name = "userId") { 6 type = NavType.StringType 7 nullable = true // 必须要允许为空 8 // defaultValue = "0" // 可提供默认值 9 } 10 ) 11) { navBackStackEntry -> 12 val userId = navBackStackEntry.arguments?.getString("userId") // 可能为 null 13 ScreenB(userId = userId) { 14 navController.navigate(route = SCREEN_C) 15 } 16} 17
跳转时,可以携带参数,也可以不传入:
1composable(route = SCREEN_A) { 2 ScreenA { 3 // 带参数跳转 4 navController.navigate(route = "${SCREEN_B}?userId=456") 5 6 // 不带参数跳转 7 navController.navigate(route = SCREEN_B) 8 } 9} 10
页面间返回结果
从 A 跳转到 B,如果还需要在 B 页面返回一个结果给 A,我们可以使用 SavedStateHandle 来完成
修改 ScreenB,使其能够返回结果:
1@Composable 2fun ScreenB(modifier: Modifier = Modifier, onClick: (String) -> Unit = {}) { 3 Column( 4 modifier = modifier.fillMaxSize(), 5 horizontalAlignment = Alignment.CenterHorizontally, 6 verticalArrangement = Arrangement.Center 7 ) { 8 Text("这里是页面 B") 9 Button(onClick = { onClick("这是B返回的结果") }) { 10 Text("返回结果") 11 } 12 } 13} 14
修改 NavHost 中 ScreenB 的路由定义:
1composable( 2 route = SCREEN_B 3) { navBackStackEntry -> 4 ScreenB { result -> 5 // 将结果设置到上一个页面的SavedStateHandle中 6 navController.previousBackStackEntry 7 ?.savedStateHandle 8 ?.set("result_key", result) 9 // 回到上一个页面 10 navController.popBackStack() // 将当前页面弹出栈 11 } 12} 13
现在,就能够在 ScreenA 中接收结果了:
1composable(route = SCREEN_A) { navBackStackEntry -> 2 3 // 这样也能获取到当前的栈节点 4 // navController.currentBackStackEntry 5 6 // 监听栈节点的参数 7 val result = navBackStackEntry.savedStateHandle.getStateFlow<String?>( 8 key = "result_key", 9 initialValue = null 10 ).collectAsState() 11 12 ScreenA(resultFromB = result.value) { 13 // 移除参数 14 navBackStackEntry.savedStateHandle.remove<String>("result_key") 15 16 navController.navigate(route = SCREEN_B) 17 } 18} 19 20@Composable 21fun ScreenA(modifier: Modifier = Modifier, resultFromB: String?, onClick: () -> Unit) { 22 Column( 23 modifier = modifier.fillMaxSize(), 24 horizontalAlignment = Alignment.CenterHorizontally, 25 verticalArrangement = Arrangement.Center 26 ) { 27 Text("这里是页面 A") 28 resultFromB?.let { 29 Text("从B返回的结果: $it") 30 } 31 Button(onClick = onClick) { 32 Text("跳转到页面 B") 33 } 34 } 35} 36
高级导航技巧
嵌套导航图 (Nested Navigation)
当应用变得复杂时,我们可以嵌套导航图来让路由更加清晰。
1const val SCREEN_A = "screen_a" 2const val SCREEN_B = "screen_b" 3const val AUTH_FLOW_ROUTE = "auth_flow" 4const val LOGIN_ROUTE = "login" 5const val REGISTER_ROUTE = "register" 6 7@Composable 8fun MyComplexApp(modifier: Modifier = Modifier) { 9 val navController = rememberNavController() 10 NavHost(navController = navController, startDestination = SCREEN_A) { 11 // 主应用页面 12 composable(route = SCREEN_A) { 13 Column( 14 modifier = Modifier.fillMaxSize(), 15 horizontalAlignment = Alignment.CenterHorizontally, 16 verticalArrangement = Arrangement.Center 17 ) { 18 Text("A") 19 Button(onClick = { 20 // 将跳转到子图的起始页 (LOGIN_ROUTE) 21 navController.navigate(route = AUTH_FLOW_ROUTE) 22 }) { 23 Text("跳转到登录页") 24 } 25 } 26 } 27 composable(route = SCREEN_B) { 28 Text("B") 29 } 30 31 // 子导航图-认证流程 32 navigation( 33 startDestination = LOGIN_ROUTE, // 子图的起始页 34 route = AUTH_FLOW_ROUTE // 子图的路由名 35 ) { 36 composable(route = LOGIN_ROUTE) { 37 Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 38 Text("Login") 39 } 40 } 41 composable(route = REGISTER_ROUTE) { 42 Text("Register") 43 } 44 } 45 } 46 47} 48
导航完成时,回退栈将会是 [ SCREEN_A, LOGIN_ROUTE ]。
ViewModel 作用域
默认情况下,ViewModel 的生命周期绑定到了 NavBackStackEntry 返回栈节点。
如果我们希望多个页面(Composable)共享一个 ViewModel,我们可以提升 ViewModel 的作用域到这些屏幕共同的嵌套导航图。
比如我们希望登录和注册页面能够共享一个邮箱数据,我们可以这样做:
1class SharedAuthViewModel : ViewModel() { 2 3 // 邮箱 4 private val _email = MutableStateFlow("") 5 val email: StateFlow<String> = _email 6 7 fun updateEmail(newEmail: String) { 8 _email.value = newEmail 9 } 10 11 init { 12 Log.d("AuthViewModel", "ViewModel created!") 13 } 14 15 override fun onCleared() { 16 super.onCleared() 17 Log.d("AuthViewModel", "ViewModel destroyed!") 18 } 19} 20 21class MainActivity : ComponentActivity() { 22 override fun onCreate(savedInstanceState: Bundle?) { 23 super.onCreate(savedInstanceState) 24 setContent { 25 ComposeNavigationTestTheme { 26 MyApp() 27 } 28 } 29 } 30} 31 32const val HOME_ROUTE = "home" 33const val AUTH_FLOW_ROUTE = "auth_flow" // 嵌套图的路由 34const val LOGIN_ROUTE = "login" 35const val REGISTER_ROUTE = "register" 36 37@Composable 38fun MyApp() { 39 val navController = rememberNavController() 40 41 NavHost(navController = navController, startDestination = HOME_ROUTE) { 42 composable(HOME_ROUTE) { 43 HomeScreen { 44 navController.navigate(AUTH_FLOW_ROUTE) 45 } 46 } 47 48 // 认证流程子图 49 navigation( 50 startDestination = LOGIN_ROUTE, 51 route = AUTH_FLOW_ROUTE 52 ) { 53 composable(LOGIN_ROUTE) { navBackStackEntry -> 54 // 获取子图的 NavBackStackEntry 55 val authGraphEntry = remember(navBackStackEntry) { 56 val parentRoute = navBackStackEntry.destination.parent!!.route!! 57 navController.getBackStackEntry(parentRoute) 58 } 59 // 将 ViewModel 作用域限定到这个子图 60 val authViewModel: SharedAuthViewModel = viewModel(authGraphEntry) 61 62 LoginScreen( 63 viewModel = authViewModel, 64 toRegister = { navController.navigate(REGISTER_ROUTE) }, 65 toHome = { 66 // 回到主页 67 navController.popBackStack(HOME_ROUTE, inclusive = false) 68 }) 69 } 70 71 composable(REGISTER_ROUTE) { navBackStackEntry -> 72 val authGraphEntry = remember(navBackStackEntry) { 73 val parentRoute = navBackStackEntry.destination.parent!!.route!! 74 navController.getBackStackEntry(parentRoute) 75 } 76 // 与 LoginScreen 获取到完全相同的 ViewModel 实例 77 val authViewModel: SharedAuthViewModel = viewModel(authGraphEntry) 78 79 RegisterScreen(viewModel = authViewModel) { 80 // 回到登录页 81 navController.navigate(LOGIN_ROUTE) { 82 popUpTo(route = LOGIN_ROUTE) { 83 inclusive = true 84 } 85 } 86 // 或者直接使用 navController.popBackStack() 87 } 88 } 89 } 90 } 91} 92 93@Composable 94fun HomeScreen(onClick: () -> Unit = {}) { 95 Column( 96 modifier = Modifier.fillMaxSize(), 97 horizontalAlignment = Alignment.CenterHorizontally, 98 verticalArrangement = Arrangement.Center 99 ) { 100 Text("主屏幕") 101 Button(onClick = onClick) { 102 Text("进入登录/注册流程") 103 } 104 } 105} 106 107@Composable 108fun LoginScreen( 109 viewModel: SharedAuthViewModel, 110 toRegister: () -> Unit, 111 toHome: () -> Unit 112) { 113 val email by viewModel.email.collectAsState() 114 115 Column( 116 modifier = Modifier 117 .fillMaxSize() 118 .padding(16.dp), 119 horizontalAlignment = Alignment.CenterHorizontally, 120 verticalArrangement = Arrangement.Center 121 ) { 122 Text("登录页") 123 TextField( 124 value = email, 125 onValueChange = { viewModel.updateEmail(it) }, 126 label = { Text("共享的 Email") } 127 ) 128 Spacer(Modifier.height(16.dp)) 129 Button(onClick = toRegister) { 130 Text("跳转到注册页") 131 } 132 Spacer(Modifier.height(8.dp)) 133 Button(onClick = toHome) { 134 Text("返回主页") 135 } 136 } 137} 138 139@Composable 140fun RegisterScreen(viewModel: SharedAuthViewModel, toLogin: () -> Unit) { 141 val email by viewModel.email.collectAsState() 142 143 Column( 144 modifier = Modifier 145 .fillMaxSize() 146 .padding(16.dp), 147 horizontalAlignment = Alignment.CenterHorizontally, 148 verticalArrangement = Arrangement.Center 149 ) { 150 Text("注册页") 151 TextField( 152 value = email, 153 onValueChange = { viewModel.updateEmail(it) }, 154 label = { Text("共享的 Email (与登录页同步)") } 155 ) 156 Spacer(Modifier.height(16.dp)) 157 Button(onClick = toLogin) { 158 Text("返回登录页") 159 } 160 } 161} 162
popBackStack(route, inclusive) 的行为是不断从栈顶弹出页面,直到找到函数参数 route,如果 inclusive 标志为 true,那么 route 也会一并弹出。
底部导航栏的状态保存与恢复
在实现底部导航栏时,切换 Tab 往往会导致前一个 Tab 的状态(如滚动位置)丢失。
为了实现状态的保存与恢复,我们可以使用三个关键选项:
1// 当点击底部 Tab 时,调用此方法 2fun NavController.onBottomNavClicked(route: String) { 3 this.navigate(route) { 4 // 弹出到图的起始点,避免回退栈无限累积 5 popUpTo(this@onBottomNavClicked.graph.findStartDestination().id) { 6 saveState = true // 关键:弹出时保存栈中页面的状态 7 } 8 // 避免在已位于栈顶时重复创建 9 launchSingleTop = true 10 // 关键:导航到目的地时,恢复其之前保存的状态 11 restoreState = true 12 } 13} 14
这样能实现状态不丢失,并且在 首页 -> 搜索 -> 我的 来回切换时,回退栈只会是这几个:首页、首页 -> 搜索、首页 -> 我的。
示例代码:
1class TabViewModel(private val tabName: String) : ViewModel() { 2 private val _count = MutableStateFlow(0) 3 val count = _count.asStateFlow() 4 5 fun increment() { 6 _count.value++ 7 } 8 9 init { 10 Log.d("ViewModelLifecycle", "$tabName ViewModel CREATED") 11 } 12 13 override fun onCleared() { 14 Log.d("ViewModelLifecycle", "$tabName ViewModel CLEARED") 15 super.onCleared() 16 } 17} 18 19// 底部 Tab 的路由 20sealed class Screen(val route: String, val icon: ImageVector, val label: String) { 21 data object Home : Screen("home", Icons.Filled.Home, "首页") 22 data object Search : Screen("search", Icons.Filled.Search, "搜索") 23 data object Profile : Screen("profile", Icons.Filled.Person, "我的") 24} 25 26val bottomNavItems = listOf( 27 Screen.Home, 28 Screen.Search, 29 Screen.Profile 30) 31 32 33class MainActivity : ComponentActivity() { 34 override fun onCreate(savedInstanceState: Bundle?) { 35 super.onCreate(savedInstanceState) 36 setContent { 37 ComposeNavigationTestTheme { 38 MainScreen() 39 } 40 } 41 } 42} 43 44@Composable 45fun MainScreen() { 46 val navController = rememberNavController() 47 Scaffold( 48 bottomBar = { 49 MyBottomNavBar(navController = navController) 50 } 51 ) { paddingValues -> 52 NavHost( 53 navController = navController, 54 startDestination = Screen.Home.route, 55 modifier = Modifier.padding(paddingValues) 56 ) { 57 composable(Screen.Home.route) { 58 // 手动创建 factory 来传递 tabName,以便观察日志 59 val vm: TabViewModel = viewModel(factory = TabViewModelFactory("Home")) 60 TabScreenContent(vm) 61 } 62 composable(Screen.Search.route) { 63 val vm: TabViewModel = viewModel(factory = TabViewModelFactory("Search")) 64 TabScreenContent(vm) 65 } 66 composable(Screen.Profile.route) { 67 val vm: TabViewModel = viewModel(factory = TabViewModelFactory("Profile")) 68 TabScreenContent(vm) 69 } 70 } 71 } 72} 73 74class TabViewModelFactory(private val tabName: String) : ViewModelProvider.Factory { 75 override fun <T : ViewModel> create(modelClass: Class<T>): T { 76 if (modelClass.isAssignableFrom(TabViewModel::class.java)) { 77 @Suppress("UNCHECKED_CAST") 78 return TabViewModel(tabName) as T 79 } 80 throw IllegalArgumentException("Unknown ViewModel class") 81 } 82} 83 84 85// 底部导航栏 86@Composable 87fun MyBottomNavBar(navController: NavController) { 88 NavigationBar { 89 // 当前路由状态 90 val navBackStackEntry by navController.currentBackStackEntryAsState() 91 val currentRoute = navBackStackEntry?.destination?.route 92 93 bottomNavItems.forEach { screen -> 94 NavigationBarItem( 95 selected = currentRoute == screen.route, 96 onClick = { 97 navController.navigate(screen.route) { 98 // 弹出到图的起始点,在这里相当于是: 99 // popUpTo(Screen.Home.route) { 100 // saveState = true 101 // inclusive = false 102 // } 103 popUpTo(navController.graph.findStartDestination().id) { 104 // 关键:弹出屏幕时,保存它们的状态 105 saveState = true 106 } 107 // 避免在已位于栈顶时重复创建实例 108 launchSingleTop = true 109 // 关键:导航到目的地时,恢复其之前保存的状态 110 restoreState = true 111 } 112 }, 113 icon = { Icon(screen.icon, contentDescription = screen.label) }, 114 label = { Text(screen.label) } 115 ) 116 } 117 } 118} 119 120// Tab 屏幕内容 121@Composable 122fun TabScreenContent(viewModel: TabViewModel) { 123 val count by viewModel.count.collectAsState() 124 val listState = rememberLazyListState() // 用于演示滚动位置的保存 125 126 Column(modifier = Modifier.fillMaxSize()) { 127 Text(text = "当前计数值: $count", style = MaterialTheme.typography.headlineMedium) 128 Button(onClick = { viewModel.increment() }) { 129 Text("点我 +1") 130 } 131 132 LazyColumn(state = listState) { 133 items(100) { 134 Text(text = "我是列表项 $it", modifier = Modifier.padding(16.dp)) 135 } 136 } 137 } 138} 139
在这里起始页是 home,假设当前的返回栈是 [home, profile],点击 search 标签页时将会发生:
popUpTo(navController.graph.findStartDestination().id):将除了起始页(home)的页面都弹出栈,返回栈会变为[home]。(inclusive默认为false)saveState = true:将弹出的页面的ViewModel和 UI 状态进行保存。launchSingleTop = true:防止重复点击同一个 Tab 时,创建多个页面实例。restoreState = true:导航到search页时,会先看看之前有没有为search页保存过状态。- 如果有,会直接唤醒之前的
search实例,恢复其ViewModel和 UI 状态(这里是滚动位置)。 - 如果没有,意味着是第一次点击,会创建一个全新的
search页面实例。
- 如果有,会直接唤醒之前的
支持 deep Link
deep link 就是可以让第三方(如浏览器、其他应用等)直接唤起指定的页面。
首先,让当前的 Activity 支持 deep link,修改 AndroidManifest.xml 文件:
1<activity 2 android:name=".MainActivity" 3 android:exported="true" 4 android:theme="@style/Theme.ComposeNavigationTest"> 5 <intent-filter> 6 <action android:name="android.intent.action.MAIN" /> 7 8 <category android:name="android.intent.category.LAUNCHER" /> 9 </intent-filter> 10 11 <intent-filter> 12 <action android:name="android.intent.action.VIEW" /> 13 14 <category android:name="android.intent.category.DEFAULT" /> 15 <category android:name="android.intent.category.BROWSABLE" /> 16 17 <!-- 支持 myapp://screen_b 格式--> 18 <data 19 android:host="screen_b" 20 android:scheme="myapp" /> 21 </intent-filter> 22</activity> 23
然后在 ScreenB 页面的路由定义处加上 deep link 的支持即可。
1composable( 2 route = "${SCREEN_B}/{userId}", 3 arguments = listOf( 4 navArgument(name = "userId") { 5 type = NavType.StringType 6 } 7 ), 8 // 添加 deepLinks 列表 9 deepLinks = listOf( 10 navDeepLink { 11 // 定义 URI 格式,它会自动匹配路由参数 12 uriPattern = "myapp://$SCREEN_B/{userId}" 13 } 14 ) 15) { navBackStackEntry -> 16 val userId = navBackStackEntry.arguments?.getString("userId") 17 ScreenB(userId = userId) { 18 navController.navigate(route = SCREEN_C) 19 } 20} 21
只要进入 "myapp://screen_b/321" 链接,即可在打开页面 B 的同时传递 userId 为 "321" 的参数。
参考
参考郭霖大佬的博客:写给初学者的Jetpack Compose教程,Navigation
《Jetpack Compose Navigation 2.x 详解》 是转载文章,点击查看原文。
