在Android布局还在使用XML时代,我们基于Intent来实现页面的跳转;在Compose时代,我们通过什么来实现页面的跳转呢?本文主要介绍以下几点内容。
1. Navigation是什么
2. A界面如何跳转到B界面
3. A界面如何携带参数跳转到B界面
4. 底部导航栏如何实现跳转
前置条件: 1、引入navigation(可跳过)
navigation-compose = "2.5.3" //libs.version.tomal 文件
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } //libs.version.tomal 文件
implementation(libs.androidx.navigation.compose) //app 目录下build.gradle.kts
2、创建简单的页面:
@Composable
fun PageA() {
Text("page A")
}
@Composable
fun PageB() {
Text("page B")
}
@Composable
fun PageC() {
Text("page C")
}
@Composable
fun PageD() {
Text("page D")
}
一、 Navigation是什么?
1、Navigation 最早出现在Jetpack 库中,它主要管理应用内导航,提供一种声明式的方式来定义导航路径,简化了组件之间的切换过程;在Compose中Navigation库同样适用,并专门为Compose设计并扩展。
2、4个核心概念(这可比Intent多多了)
2.1、NavController 导航的核心控制器,用于管理导航栈以及执行导航操作。
2.2、NavHost一个容器,用于显示导航图中的目的地。
2.3、Destination(目的地)导航图中的节点,(如 Composable 函数)或模块。
2.4、NavGraph(导航图)一个包含所有目的地和它们之间导航路径(动作)的资源文件。在 Compose 中,我们通常使用 Kotlin DSL 来构建导航图。
名词的解释和理解,总是晦涩难懂的,下面通过一个例子来解释上面的名词;柳如烟从成都出发自驾到重庆,柳如烟就是NavController她负责管理导航的堆栈信息和执行导航操作;她驾驶的玛莎拉蒂就是NavHost(一个容器,用于显示导航图中的目的地);Destination(目的地)导航图中的节点,就是如烟要去的重庆;NavGraph(导航图 如composable)就是如烟使用的xx导航地图。(ps:最近迷上短剧,重生之我在敲代码)
二、A界面如何跳转到B界面
2.1 简单修改A界面代码
@Composable
fun PageA() {
Column(modifier = Modifier.padding(10.dp)) {
Button(onClick = {
}) {
Text("跳转到B界面")
}
}
}
2.2创建NavHostController
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navHostController = rememberNavController()
AppScreen(navHostController)
}
}
}
@Composable
fun AppScreen(navHostController:NavHostController) {
}
2.3创建NavHost和NavGraph
@Composable
fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Route.PAGE_A
) {
composable(Route.PAGE_A) {
PageA()
}
composable(Route.PAGE_B) {
PageA()
}
composable(Route.PAGE_C) {
PageB()
}
}
}
2.4修改MainActivity AppScreen
@Composable
fun AppScreen(navHostController:NavHostController) {
NavGraph(navHostController)
}
解释一下上面的代码,首先MainActivity 通过setContent 关联到了AppScreen,AppScreen内部调用了NavGraph,NavGrap内部实现了一个NavHost(如烟的玛莎拉蒂)通过导航图(composeable )关联到了其它界面,startDestination就是起始地,默认显示哪个; 也就是说MainActivity默认显示PageA的内容。
2.5 从界面A跳转到界面B
完成导航的具体动作是通过NavController 来执行的,也就是如烟本人想去哪里,由她自己决定。
2.5.1 修改PageA 和NavGraph中的代码来实现跳转
@Composable
fun PageA(onButtonClick:()->Unit) {
Column(modifier = Modifier.padding(10.dp)) {
Button(onClick = {
onButtonClick.invoke()
}) {
Text("跳转到B界面")
}
}
}
composable(Route.PAGE_A) {
PageA{
navController.navigate(Route.PAGE_B)
}
}
以上就实现了通过NavController来实现最基本的跳转。
三、A界面如何携带参数跳转到B界面
3.1夸界面的参数传递
和Intent一样,一个发送方,一个接收方;发送方通过路径+/$来站位,接收方通过路径+/{}来站位,此外接收方还需要通过backStackEntry.arguments?.getXXX接收参数;
如果有多个参数,发送方需要有多个 /param1/$param2,(这里显示有问题,具体参见代码)同理接收方也需要有多个/{}来占位例如/{param1}/{param2}",并且需要有多个backStackEntry.arguments?.getXXX来接收参数()
3.2修改PageA 和NavGraph中的代码来实现携带参数的跳转
@Composable
fun PageA(onButtonClick:()->Unit,enterPageC:(String,String)->Unit) {
Column(modifier = Modifier.padding(10.dp)) {
Button(onClick = {
onButtonClick.invoke()
}) {
Text("跳转到B界面")
}
Button(onClick = {
enterPageC.invoke("Hello","Android")
}) {
Text("跳转到C界面")
}
}
}
@Composable
fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Route.PAGE_A
) {
composable(Route.PAGE_A) {
PageA(onButtonClick = {
navController.navigate(Route.PAGE_B)
}, enterPageC = {param1,param2->
navController.navigate(Route.PAGE_C+"/$param1/$param2")
})
}
composable(
Route.PAGE_C + "/{param1}/{param2}",
) { backStackEntry ->
val param1 = backStackEntry.arguments?.getString("param1")
val param2 = backStackEntry.arguments?.getString("param2")
PageC(param1,param2)
}
composable(Route.PAGE_B) {
PageB()
}
}
}
运行效果如下:
3.3 有没有觉得上面的路径拼接很麻烦并且还要很小心,如果拼接错误,就不能传递参数了,有没有解决方法呢,当然有我们新建一个NavController的扩展方法
fun NavController.navigate(route:String,args: Bundle?){
graph.findNode(route)?.let {
navigate(it.id,args)
}
}
然后再修改 NavGraph 中的代码
@Composable
fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Route.PAGE_A
) {
composable(Route.PAGE_A) {
PageA(onButtonClick = {
navController.navigate(Route.PAGE_B)
}, enterPageC = {param1,param2->
// navController.navigate(Route.PAGE_C+"/$param1/$param2")
navController.navigate(Route.PAGE_C,
bundleOf(
"param1" to param1,
"param2" to param2
)
)
})
}
composable(
Route.PAGE_C,
) { backStackEntry ->
backStackEntry.arguments?.apply {
val param1 =getString("param1")
val param2 =getString("param2")
PageC(param1,param2)
}
}
composable(Route.PAGE_B) {
PageB()
}
}
}
是不是代码清爽了许多,运行效果依然一样。
四、 底部导航栏如何实现跳转
4.0引入依赖: toml文件下新增:
compose-material = "1.6.0"
androidx-navigation-compose-runtime = { module = "androidx.compose.material:material", version.ref = "compose-material" }
app build.gradle新增
implementation(libs.androidx.navigation.compose.runtime)
新增HomePage、Project、Mine页面,界面都非常简单,就是一个Text就不在赘述了;修改界面D的代码,让界面D作为HomePage、Project、Mine的容器,通过BottomNavigation 来实现界面的跳转。
@Composable
fun PageD(navController:NavHostController) {
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
BottomBar(navController)
}
) { contentPadding->
NavGraph(navController)
}
}
val navBarItems = listOf(
NavBarItem.Home,
NavBarItem.Project,
NavBarItem.Mine
)
sealed class NavBarItem(val label: String, val icon: Int, val route: String) {
object Home : NavBarItem("首页", R.drawable.ic_tab_home, Route.HOME)
object Project : NavBarItem("项目", R.drawable.ic_tab_project, Route.PROJECT)
object Mine : NavBarItem("我的", R.drawable.ic_tab_mine, Route.MINE)
}
@Composable
fun BottomBar(navHostController: NavHostController){
BottomNavigation(
backgroundColor = MaterialTheme.colorScheme.primary
){
val navBackStackEntry by navHostController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
navBarItems.forEach { item->
BottomNavigationItem(
selected = currentRoute == item.route,
icon = {
Icon(
painter = painterResource(id = item.icon),
contentDescription = item.route,
modifier = Modifier
.size(22.dp)
.padding(bottom = 4.dp)
)
},
selectedContentColor = MaterialTheme.colorScheme.onPrimary,
unselectedContentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.3f),
label = {
Text(
text = item.label,
fontSize = 12.sp
)
},
onClick = {
navHostController.navigate(item.route){
navHostController.graph.startDestinationRoute?.let { route->
popUpTo(route){
saveState=true
}
}
launchSingleTop=true
restoreState=true
}
}
)
}
}
}
NavGraph 文件中新增代码
@Composable
fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Route.PAGE_A
) {
composable(Route.PAGE_A) {
PageA(onButtonClick = {
navController.navigate(Route.PAGE_B)
}, enterPageC = {param1,param2->
// navController.navigate(Route.PAGE_C+"/$param1/$param2")
navController.navigate(Route.PAGE_C,
bundleOf(
"param1" to param1,
"param2" to param2
)
)
}, enterPageD = {
navController.navigate(Route.PAGE_D)
})
}
composable(
Route.PAGE_C,
) { backStackEntry ->
backStackEntry.arguments?.apply {
val param1 =getString("param1")
val param2 =getString("param2")
PageC(param1,param2)
}
}
composable(Route.PAGE_B) {
PageB()
}
composable(Route.PAGE_D){
PageD(navController)
}
composable(Route.HOME){
HomePage()
}
composable(Route.PROJECT){
Project()
}
composable(Route.MINE){
Mine()
}
}
}
运行一下看看效果
java.lang.IllegalStateException: ViewModelStore should be set before setGraph call
WTF这时什么情况,经过查阅资料得知,NavHostController不允许嵌套,那么我们在程序中嵌套了么?嵌套了,PageD(NavHostController) 和NavGraph 共用了一个NavHostController,知道产生问题的原因就好改了
@Composable
fun PageD() {
val pageDNavController = rememberNavController()
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
BottomBar(pageDNavController)
}
) { contentPadding->
BottomNavGraph(pageDNavController)
}
}
移除NavGraph中的二级代码
composable(Route.HOME){
HomePage()
}
composable(Route.PROJECT){
Project()
}
composable(Route.MINE){
Mine()
}
新增BottomNavGraph代码
@Composable
fun BottomNavGraph(navController: NavHostController){
NavHost(navController=navController, startDestination = Route.HOME){
composable(Route.HOME){
HomePage()
}
composable(Route.PROJECT){
Project()
}
composable(Route.MINE){
Mine()
}
}
}
在运行一下,运行结果与预期相符。