Jetpack Compose Navigation 2.x 详解

作者:雨白日期:2025/11/15

简单的页面跳转

在 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

运行效果:

Screen_recording_20251115_151253.gif

但当前的效果只有页面切换,没有页面导航。按下返回键时,只会回到桌面,无法回到上一个页面。

当然,你可以自己来实现导航功能,但我们更多会使用 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

这样就具备导航功能了,运行效果:

Screen_recording_20251115_203503.gif

当前所在页面

要知道当前所在页面,可以使用 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 上面的 BC,并且不希望有两个 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

当指定 launchSingleToptrue 时,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

运行效果:

image.png

可选参数

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

修改 NavHostScreenB 的路由定义:

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 详解》 是转载文章,点击查看原文


相关推荐


深度测评解析 CANN:从 ACL 到自定义算子,解锁昇腾计算的全部潜能
wei_shuo2025/11/14

深度测评解析 CANN:从 ACL 到自定义算子,解锁昇腾计算的全部潜能 CANN 核心价值解读:统一计算底座与全场景适配 ✅端到端栈级支持:CANN 覆盖驱动、运行时、算子加速库、编译器及上层框架适配的全套工具链,大幅降低模型向昇腾硬件移植的工程成本 ✅开发者定制接口:ACL 提供 C/C++、Python 双接口,兼顾快速原型验证与生产级接入;AOL(aclnn)算子性能强,支持两段式调用,方便做内存、离线编译优化 ✅可控资源调度与并发:通过 Device/Context/Stream


Python 的内置函数 input
IMPYLH2025/11/13

Python 内建函数列表 > Python 的内置函数 input Python 的内置函数 input() 是一个用于获取用户输入的标准函数,它会暂停程序执行,等待用户在控制台输入内容并按回车键确认。这个函数在交互式程序和需要用户参与的脚本中非常有用。 基本用法 input() 函数的基本语法如下: user_input = input([prompt]) 其中: prompt 是一个可选参数,用于显示提示信息,告诉用户需要输入什么内容函数返回用户输入的内容,以字符串形式保存


C++笔记——STL list
报错小能手2025/11/11

1. list 基本概念 什么是list? 双向链表:每个元素包含指向前后元素的指针,形成链式结构 核心特性(与vector/deque对比): 特性vectordequelist随机访问✅ O(1)✅ O(1)❌ O(n)头部插入删除❌ O(n)✅ O(1)✅ O(1)尾部插入删除✅ O(1)✅ O(1)✅ O(1)中间插入删除❌ O(n)❌ O(n)✅ O(1)内存布局连续分段连续完全分散 2. list 的基本使用 头文件和声明: cpp #include <list


各个系统的 docker安装
Qayrup2025/11/9

docker简介 Docker 是一组平台即服务的产品,它基于操作系统层级的虚拟化技术,将软件与其依赖项打包为容器。本文将介绍在 CentOS 8 操作系统中安装 Docker 服务,并解决镜像源无法访问的问题。 centos8 安装 1 检查版本 Docker 要求 CentOS 的内核版本至少高于 3.10,可以用命令uname -r查看 2 安装 Docker 所需依赖 //执行命令yum install -y yum-utils device-mapper-persistent-dat


理解PostgreSQL中的数据块
WarriorTan2025/11/7

PG的数据块大小,默认是8KB,可以调整为16K或者 32K吗? PostgreSQL的数据块大小默认为8KB,可以将其调整为16KB或32KB。数据块大小需要在‌编译安装‌PostgreSQL时通过配置参数指定,例如使用configure.ac中的--with-blocksize选项进行设置 。需要注意的是,一旦数据库初始化完成,数据块大小就无法再修改 。 数据块的行指针都包括哪些信息? 具体来说,行指针是一个32位的数字,其结构被划分为三个部分: 行内容的偏移量‌:占用15位(bit


C#.NET Random 深入解析:随机数生成原理与最佳实践
唐青枫2025/11/4

简介 Random 是 .NET 中 System 命名空间提供的一个类,用于生成伪随机数。它广泛应用于需要随机化操作的场景,如生成随机数据、模拟、游戏开发或测试用例生成。 伪随机数生成 在计算机中,Random 类用于生成伪随机数,这些数值在一定程度上看起来是随机的,但它们实际上是通过数学公式从一个初始种子值计算得到的,因此称之为“伪随机数”。 广泛应用 Random 类常用于游戏开发、模拟、加密等场景。在许多应用中,生成随机数或随机选择某个元素是常见的需求。 注意: Random


设计模式的原则有哪些?
你的人类朋友2025/10/31

前言 温馨提示 对于原本不太熟悉设计模式的人来说(比如在下),这些内容是需要一定的时间消化的!慢慢来 😆 👋 你好啊,我是你的人类朋友! 今天说说设计模式的原则有哪些! 在开发用户权限系统时,你是否遇到过这样的问题: 当创建新的管理员用户类型时,发现它无法兼容普通用户的所有方法,导致系统中到处需要判断用户类型? 让我们了解设计模式的基本原则,构建更健壮的软件架构~ 健壮是啥意思? 健壮是指软件系统在面对变化和复杂性时,能够保持稳定运行的能力。也就是耐造的能力。 正文 SOLID 原则


Java的包装类
麦麦鸡腿堡2025/10/29

包装类(Wrapper)的分类: 1.针对八种基本数据类型相应的引用类型--包装类 2.有了类的特点,就可以调用类中的方法 *黄色框内都是number的子类,number是Ojbect子类,黑色框中的包装类是独立的,Ojbect子类 //boolean-Boolean-父类Object //char-Character-父类Object //byte-Byte-父类number-父类Object //int-Integer-父类number-父类Object //long-Long


17_AI智能体开发架构搭建之Flask集成swagger在线文档实践
腾飞开源2025/10/26

一、为什么需要Swagger集成? 在微服务架构和前后端分离的现代开发模式中,API文档承担着关键角色: 开发效率:前后端并行开发,减少沟通成本 接口契约:明确的请求/响应规范,避免歧义 测试便利:直接在文档界面测试API 团队协作:新成员快速理解接口设计 客户端生成:自动生成多种语言客户端代码 AI智能体系统设计相关文章: 👉《01_AI智能体系统设计之系统架构设计》 👉《02_AI智能体系统设计之钉钉消息处理流程设计》 👉《03_AI智能体系统设计之Ag


C#.NET SqlKata 使用详解:优雅构建动态 SQL 查询
唐青枫2025/10/23

简介 在复杂项目中,为了保持 SQL 灵活性与可读性,开发者往往需要手写大量拼接字符串或使用 ORMs 附带的 LINQ,但两者各有局限:手写拼接易出错、难以维护;LINQ 在某些场景下生成的 SQL 不够直观或性能不佳。 SqlKata 是一款轻量级、数据库无关的查询构建器(Query Builder),提供—— 流式 API,链式调用拼装 SQL 可切换编译器,支持多种数据库方言(SQL Server、PostgreSQL、MySQL、SQLite、Oracle 等) 语

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0