全面解析Jetpack Compose中的导航路由–[引用大佬川峰之作]
fun NavigationExample() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = “Welcome”) {
composable(“Welcome”) { WelcomeScreen(navController) }
composable(“Login”) { LoginScreen(navController) }
composable(“Home”) { HomeScreen(navController) }
composable(“Cart”) { CartScreen(navController) }
}
}
fun WelcomeScreen(navController : NavController) {
Column() {
Text(“WelcomeScreen”, fontSize = 20.sp)
Button(onClick = { navController.navigate(“Login”) }) {
Text(text = “Go to LoginScreen”)
}
}
}
fun NavigationExample2() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = “Welcome”) {
composable(“Welcome”) {
WelcomeScreen {
navController.navigate(“Login”)
}
}
…
}
}
@Composable
fun WelcomeScreen(onGotoLoginClick: () -> Unit = {}) {
Column() {
Text(“WelcomeScreen”, fontSize = 20.sp)
Button(onClick = onGotoLoginClick) {
Text(text = “Go to LoginScreen”)
}
}
}
navController.navigate(“Home”){
popUpTo(“Welcome”)
}
// 同上,包含Welcome
navController.navigate(“Home”){
popUpTo(“Welcome”){ inclusive = true }
}
// 当前栈顶已经是Home时,不再入栈新的Home节点,相当于Activity的SingleTop启动模式
navController.navigate(“Home”){
launchSingleTop = true
}
popUpTo(“Welcome”) { inclusive = true}
}
fun NavigationExample2() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = “Welcome”) {
composable(“Login”) {
val context = LocalContext.current
LoginScreen {
try {
navController.navigate(“Home”) {
popUpTo(“Welcome”) { inclusive = true}
}
} catch (e : IllegalArgumentException) {
// 路由不存在时会抛异常
Log.e(“TAG”, “NavigationExample2: $e”)
with(context) { showToast(“Home路由不存在!”)}
}
}
}
…
}
}
route: String,
onNavigateFailed: ((IllegalArgumentException)->Unit)?,
builder: NavOptionsBuilder.() -> Unit
) {
try {
this.navigate(route, builder)
} catch (e : IllegalArgumentException) {
onNavigateFailed?.invoke(e)
}
}
// 使用:
LoginScreen {
navController.navigateWithCall(
route = “Home”,
onNavigateFailed = { with(context) { showToast(“Home路由不存在!”)} }
) {
popUpTo(“Welcome”) { inclusive = true}
}
}
fun NavigationWithParamsExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = “Home”) {
composable(“Home”) {
HomeScreen1 { userId, isFromHome ->
navController.navigate(“List/$userId/$isFromHome”)
}
}
composable(
“List/{userId}/{isFromHome}”,
arguments = listOf(
navArgument(“userId”) { type = NavType.IntType }, // 设置参数类型
navArgument(“isFromHome”) {
type = NavType.BoolType
defaultValue = false // 设置默认值
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt(“userId”) ?: -1
val isFromHome = backStackEntry.arguments?.getBoolean(“isFromHome”) ?: false
ListScreen(userId, isFromHome) { id ->
navController.navigate(“Detail/$id”)
}
}
composable(“Detail/{detailId}”) { backStackEntry ->
val detailId = backStackEntry.arguments?.getString(“detailId”)
DetailScreen(detailId) {
navController.popBackStack()
}
}
}
}
navController.navigate(“List2/$userId”) // 可以不传$isFromHome
“List2/{userId}?fromHome={isFromHome}”, // 设置可选参数时,必须提供默认值
arguments = listOf(
navArgument(“userId”) { type = NavType.IntType },
navArgument(“isFromHome”) {
type = NavType.BoolType
defaultValue = false
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt(“userId”) ?: -1
val isFromHome = backStackEntry.arguments?.getBoolean(“isFromHome”) ?: false
ListScreen(userId, isFromHome) { id ->
navController.navigate(“Detail/$id”)
}
}
data class User(val userId : Int, val name : String): Parcelable
@Composable
fun NavigationWithParamsExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = “Home”) {
composable(“Home”) {
HomeScreen1 { userId, isFromHome ->
// 传递序列化参数
val user = User(56789, “小明”)
navController.navigate(“List3/$user”) // NOT SUPPORTED!!!
}
}
// NOT SUPPORTED!!! navigation-compose暂不支持直接传Parcelable
composable(
“List3/{user}”, // 传递Parcelable数据类
arguments = listOf(
navArgument(“user”) { type = NavType.ParcelableType(User::class.java) },
)
) { backStackEntry ->
val user : User? = backStackEntry.arguments?.getParcelable(“user”)
user?.run {
ListScreen(userId, true) { id ->
navController.navigate(“Detail/$id”)
}
}
}
}
}
@Composable
fun NavigationWithParamsExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = “Home”) {
composable(“Home”) {
HomeScreen1 { userId, isFromHome ->
// 传递序列化参数
val user2 = User2(987654321, “小明”)
navController.navigate(“List5/$user2”) // NOT SUPPORTED!!!
}
}
// NOT SUPPORTED!!! navigation-compose暂不支持直接传Serializable
composable(
“List5/{user}”, // 传递Serializable数据类
arguments = listOf(
navArgument(“user”) { type = NavType.SerializableType(User2::class.java) },
)
) { backStackEntry ->
val user : User2? = backStackEntry.arguments?.getSerializable(“user”) as User2?
user?.run {
ListScreen(userId, true) { id ->
navController.navigate(“Detail/$id”)
}
}
}
}
}
2.使用共享的 ViewModel 实例保存数据类对象(mutableStateOf),发起方向共享的 ViewModel 实例中赋值新的数据类对象,接受方从共享的 ViewModel 实例中读取数据类对象。
3.通过 navController.previousBackStackEntry?.savedStateHandle?.set(key, value)/get(key) 解决,但是这种有缺点就是跳转之前先弹了回退栈就获取不到了。(所以这种方案只能是在一定条件下可行)
4.使用开源库 compose-destinations,这个库非常棒,使用非常简化(后面会介绍如果使用)
data class User(val userId : Int, val name : String): Parcelable
@Composable
fun NavigationWithParamsExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = “Home”) {
composable(“Home”) {
HomeScreen1 { userId, isFromHome ->
val user = User(56789, “小明”)
navController.currentBackStackEntry?.savedStateHandle?.set(“user”, user)
navController.navigate(“List4”)
}
}
composable(
“List4”,
) { backStackEntry ->
val user = navController.previousBackStackEntry?.savedStateHandle?.get<User>(“user”)
user?.run {
ListScreen(userId, true) { id ->
navController.navigate(“Detail/$id”)
}
}
println(“user == null is ${user == null}”)
}
}
}
data class User(val userId : Int, val name : String): Parcelable
@Composable
fun NavigationWithParamsExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = “Home”) {
composable(“Home”) {
HomeScreen1 { userId, isFromHome ->
val user = User(56789, “小明”)
navController.currentBackStackEntry?.savedStateHandle?.set(“user”, user)
navController.navigate(“List4”) {
popUpTo(“Home”) {inclusive = true}
}
}
}
composable(
“List4”,
) { backStackEntry ->
val user = navController.previousBackStackEntry?.savedStateHandle?.get<User>(“user”)
user?.run {
ListScreen(userId, true) { id ->
navController.navigate(“Detail/$id”)
}
}
if (user == null) {
with(LocalContext.current) { showToast(“user == null”) }
}
}
}
}
// …
id ‘com.google.devtools.ksp’ version ‘1.7.20-1.0.8’
}
ksp ‘io.github.raamcosta.compose-destinations:ksp:1.7.27-beta’
…
// replace applicationVariants with libraryVariants if the module uses ‘com.android.library’ plugin!
applicationVariants.all { variant ->
kotlin.sourceSets {
getByName(variant.name) {
kotlin.srcDir(“build/generated/ksp/${variant.name}/kotlin”)
}
}
}
}
@Destination
@Composable
fun FirstScreen(navigator: DestinationsNavigator) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“FirstScreen”, fontSize = 20.sp)
Button(onClick = {
// TODO
}) {
Text(text = “Go to SecondScreen”)
}
}
}
@Destination
@Composable
fun SecondScreen(
navigator: DestinationsNavigator,
id: Int,
name: String?,
isOwnUser: Boolean = false
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“SecondScreen”, fontSize = 20.sp)
Text(“$id $name $isOwnUser”, fontSize = 20.sp)
Button(onClick = {
// TODO
}) {
Text(text = “Go to ThirdScreen”)
}
}
}
@Destination
@Composable
fun ThirdScreen(
navigator: DestinationsNavigator,
person: Person
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“ThirdScreen”, fontSize = 20.sp)
Text(“$person “, fontSize = 20.sp)
}
}
navigator.navigate(ThirdScreenDestination(person)) // 传递对象类型参数
data class Person(val userId : Int, val name : String): Parcelable
@Serializable
data class People(val userId : Int, val name : String)
data class Man(val userId : Int, val name : String): java.io.Serializable
@Composable
fun NavigationWithParamsByDestinationsLib() {
DestinationsNavHost(navGraph = NavGraphs.root)
}
@RootNavGraph(start = true)
@Destination
@Composable
fun FirstScreen(navigator: DestinationsNavigator) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“FirstScreen”, fontSize = 20.sp)
Button(onClick = {
navigator.navigate(SecondScreenDestination(id = 789, “王小明”, true))
}) {
Text(text = “Go to SecondScreen”)
}
}
}
@Destination
@Composable
fun SecondScreen(
navigator: DestinationsNavigator,
id: Int,
name: String?,
isOwnUser: Boolean = false
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“SecondScreen”, fontSize = 20.sp)
Text(“$id $name $isOwnUser”, fontSize = 20.sp)
Button(onClick = {
val person = Person(1234567, “Android”)
navigator.navigate(ThirdScreenDestination(person))
}) {
Text(text = “Go to ThirdScreen”)
}
}
}
@Destination
@Composable
fun ThirdScreen(
navigator: DestinationsNavigator,
person: Person
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“ThirdScreen”, fontSize = 20.sp)
Text(“$person “, fontSize = 20.sp)
Button(onClick = {
val people = People(7654321, “Kotlin”)
navigator.navigate(FourthScreenDestination(people))
}) {
Text(text = “Go to FourthScreen”)
}
}
}
@Destination
@Composable
fun FourthScreen(
navigator: DestinationsNavigator,
people: People
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“FourthScreen”, fontSize = 20.sp)
Text(“$people”, fontSize = 20.sp)
Button(onClick = {
val man = Man(8866999, “Compose”)
navigator.navigate(FifthScreenDestination(man))
}) {
Text(text = “Go to FifthScreen”)
}
}
}
@Destination
@Composable
fun FifthScreen(
navigator: DestinationsNavigator,
man: Man
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“FifthScreen”, fontSize = 20.sp)
Text(“$man”, fontSize = 20.sp)
Button(onClick = {
navigator.popBackStack(FirstScreenDestination, inclusive = false)
}) {
Text(text = “Back To Home”)
}
}
}
@Composable
fun NavigateBackWithResultExample() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = “screen01”) {
composable(“screen01”) { entry ->
val text = entry.savedStateHandle.get<String>(KEY) // 尝试从NavBackStackEntry中获取值
Screen01(text) {
navController.navigate(“screen02”)
}
}
composable(“screen02”) {
Screen02 { result ->
navController.previousBackStackEntry?.savedStateHandle?.set(KEY, result) // 向前一个Entry回传结果
navController.popBackStack()
}
}
}
}
@Composable
fun Screen01(text: String?, onNavigateBtnClick: () -> Unit = {}) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
Text(text = “当前页面 Screen01”, fontSize = 16.sp)
text?.let { Text(text = “来自screen02的结果:$text”, fontSize = 16.sp) }
Button(onClick = onNavigateBtnClick) {
Text(text = “Go to screen02”)
}
}
}
@Composable
fun Screen02(onNavigateBtnClick: (String) -> Unit = {}) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
var text by remember { mutableStateOf(“”) }
Text(text = “当前页面 Screen02”, fontSize = 16.sp)
OutlinedTextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.width(300.dp)
)
Button(onClick = { onNavigateBtnClick(text) }) {
Text(text = “Go Back”)
}
}
}
object Home : Screen(“home”, “Home”)
object Favorite : Screen(“favorite”, “Favorite”)
object Profile : Screen(“profile”, “Profile”)
object Cart : Screen(“cart”, “Cart”)
}
val items = listOf(
Screen.Home,
Screen.Favorite,
Screen.Profile,
Screen.Cart
)
@Composable
fun WorkWithBottomNavigationExample() {
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
// 从 NavHost 函数中获取 navController 状态,并与 BottomNavigation 组件共享此状态。
// 这意味着 BottomNavigation 会自动拥有最新状态。
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination // 这个目的是为了下面比较获得当前的选中状态
items.forEach { screen ->
BottomNavigationItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(screen.title) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
// 加这个可解决问题:按back键会返回2次,第一次先返回home, 第二次才会退出
navController.popBackStack()
navController.navigate(screen.route) {
// 点击item时,清空栈内 popUpTo ID到栈顶之间的所有节点,避免站内节点持续增加
popUpTo(navController.graph.findStartDestination().id) {
saveState = true // 用于页面状态的恢复
}
// 避免多次重复点击按钮时产生多个实例
launchSingleTop = true
// 再次点击之前选中的Item时,恢复之前的状态
restoreState = true
// 通过使用 saveState 和 restoreState 标志,当您在底部导航项之间切换时,
// 系统会正确保存并恢复该项的状态和返回堆栈。
}
}
)
}
}
}
) { innerPadding ->
NavHost(navController, startDestination = Screen.Home.route, Modifier.padding(innerPadding)) {
composable(Screen.Home.route) { HomeScreen2(navController) }
composable(Screen.Favorite.route) { FavoriteScreen(navController) }
composable(Screen.Profile.route) { ProfileScreen(navController) }
composable(Screen.Cart.route) { CartScreen2(navController) }
}
}
}
fun WorkWithModulesExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = “home”) {
//…
// 当调用 navigate(‘home’) 时,会自动将home模块的MessageList作为页面显示
navigation(startDestination = “MessageList”, route = “home”) {
composable(“MessageList”) { MessageListScreen(navController) }
composable(“FriendList”) { FriendListScreen(navController) }
composable(“Setting”) { SettingScreen(navController) }
}
//…其他模块的设置,每个模块对应一个navigation子项
}
}
navigation(startDestination = “MessageList”, route = “home”) {
composable(“MessageList”) { MessageListScreen(navController) }
composable(“FriendList”) { FriendListScreen(navController) }
composable(“Setting”) { SettingScreen(navController) }
}
}
fun WorkWithModulesExample2() {
val navController = rememberNavController()
NavHost(navController, startDestination = “home”) {
homeGraph(navController)
//…其他模块
}
}
-
当前模块跳转到某个业务模块的某个子页面中,而不只是该模块的首页面(不管是否多 Module 还是单 Module 都存在这种需求) -
隐式跳转
@Composable
fun WorkWithDeepLinkExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = “SomeModule”) {
composable(
route = “newsDetail?id={id}”,
deepLinks = listOf(
navDeepLink {
uriPattern = “$URI/news/{id}” // 对应上面route的深度链接
action = Intent.ACTION_VIEW // 可选
}
)
) { backStackEntry ->
NewsDetailScreen(navController, backStackEntry.arguments?.getString(“id”))
}
composable(“SomeModule”) {
SomeModuleScreen {
// 在其他地方调用
val request = NavDeepLinkRequest.Builder
.fromUri(“$URI/news/1234”.toUri())
.build()
navController.navigate(request)
}
}
// …
}
}
@Composable
fun NewsDetailScreen(navController : NavController, newsId : String?) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“NewsDetailScreen $newsId”, fontSize = 20.sp)
}
}
@Composable
fun SomeModuleScreen(onNavigate : () -> Unit) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Button(onClick = onNavigate) {
Text(text = “跳转到NewsDetailScreen”)
}
}
}
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
<intent-filter>
<action android:name=”android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT” />
<category android:name=”android.intent.category.BROWSABLE” />
<data android:scheme=”my-app” android:host=”my.example.app” /> // 这里要跟定义的URI对应上
</intent-filter>
</activity>
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
“my-app://my.example.app/news/$id”.toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
var _name = mutableStateOf(“”)
val name = _name
}
@Composable
fun WorkWithViewModelExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = “example”) {
composable(“example”) { backStackEntry ->
val exampleViewModel = viewModel<ExampleViewModel>()
SomeScreen(exampleViewModel)
}
// …
}
}
@Composable
fun SomeScreen(viewModel: ExampleViewModel = viewModel()) {
}
fun MyScreen(onNavigate: (Int) -> ()) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* … */ }
}
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
fun NavigationExample2() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = “Welcome”) {
composable(“Welcome”) {
val context = LocalContext.current
WelcomeScreen {
val intent = Intent(context, OtherActivity::class.java).apply {
putExtra(“name”, “张三”)
putExtra(“uid”, 123)
}
context.startActivity(intent)
}
}
}
}
@Composable
fun WelcomeScreen(onClick: () -> Unit = {}) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“WelcomeScreen”, fontSize = 20.sp)
Button(onClick = onClick) {
Text(text = “Go to Other”)
}
}
}
fun NavigationExample2() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = “Welcome”) {
composable(“Welcome”) {
val context = LocalContext.current
var resultText by remember { mutableStateOf(“”) }
WelcomeScreen(resultText) {
val intent = Intent(context, OtherActivity::class.java).apply {
putExtra(“name”, “张三”)
putExtra(“uid”, 123)
}
if (context is Activity) {
// 以回调方式启动Activity
ActivityStarter.startForResult(context, intent, object : ActivityResultListener {
override fun onSuccess(result: Result?) {
val name = result?.data?.getStringExtra(“name”)
val uid = result?.data?.getIntExtra(“uid”, -1)
resultText = “name: $name uid: $uid”
}
override fun onFailed(result: Result?) {
}
})
}
}
}
}
}
@Composable
fun WelcomeScreen(result: String, onClick: () -> Unit = {}) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(“WelcomeScreen result: $result”, fontSize = 20.sp)
Button(onClick = onClick) {
Text(text = “Go to Other”)
}
}
}