600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 结合Navigation组件实现JetPack Compose的界面导航

结合Navigation组件实现JetPack Compose的界面导航

时间:2022-12-17 19:13:46

相关推荐

结合Navigation组件实现JetPack Compose的界面导航

Android JetPack Compose可以利用Navigation组件来实现导航

一、Navigation组件的配置

新建项目,选择Empty Compose Activity。

然后,在项目模块的build.gradle设置如下内容:

dependencies {def nav_version = "2.5.2"implementation("androidx.navigation:navigation-compose:$nav_version")......}

二、应用介绍和实体类

为了说明JetPack Compose组件的导航应用,定义一个简单的应用:即显示一个机器人滚动列表,然后点击滚动列表中的某个单项,进入具体某个机器人界面。

图1:机器人列表

通过点击列表的某行的机器人图标,进入下面的界面。

图2:显示单独机器人信息

为此创建一个表示机器人实体的类,定义如下:

data class Robot(val imageId:Int,val name:String,val description:String):Parcelable{constructor(parcel: Parcel) : this(parcel.readInt(),parcel.readString()!!,parcel.readString()!!) {}override fun writeToParcel(parcel: Parcel, flags: Int) {parcel.writeInt(imageId)parcel.writeString(name)parcel.writeString(description)}override fun describeContents(): Int =0companion object CREATOR : Parcelable.Creator<Robot> {override fun createFromParcel(parcel: Parcel): Robot {return Robot(parcel)}override fun newArray(size: Int): Array<Robot?> {return arrayOfNulls(size)}}}

三、定义不同的界面

在本应用中定义三个界面:

(1)定义滚动列表的每一单项定义在RobotItemView

/*** 定义列表单项的视图* @param robot Robot*/@Composablefun RobotItemView(robot:Robot){Column{Row(modifier= Modifier.fillMaxWidth().border(1.dp,Color.Black).clip(RoundedCornerShape(10.dp)).background(colorResource(id = R.color.teal_200)).padding(5.dp)){Image(modifier = Modifier.width(80.dp).height(80.dp).clip(shape = CircleShape).background(Color.Black).clickable {//增加导航处理},painter = painterResource(id = robot.imageId),contentDescription = "机器人")Column{Text("${robot.name}",fontSize=16.sp,color=Color.Blue)Text("${robot.description}",fontSize=20.sp,color=Color.DarkGray)}}}}

(2)定义一个显示机器人滚动列表的界面RobotListScreen

/*** Robot list screen* 定义显示机器人滚动列表的界面*/@Preview@Composablefun RobotListScreen(){val robots = mutableListOf<Robot>()for(i in 1..20)robots.add(Robot(android.R.mipmap.sym_def_app_icon,"第${i}机器人","进入机器人世界"))var reverseLayout = falseBox(modifier= Modifier.background(Color.Black).fillMaxSize()){LazyColumn(state= rememberLazyListState(),verticalArrangement = if(!reverseLayout) Arrangement.Top else Arrangement.Bottom){items(robots){robot->RobotItemView(robot = robot)}}}}

(3)定义具体的机器人信息的界面RobotScreen

/*** 定义机器人具体信息显示界面* @param robot Robot*/@Composablefun RobotScreen(){val robot = Robot(android.R.mipmap.sym_def_app_icon,"第1号机器人","第1号机器人进入机器人的世界")Column(modifier = Modifier.background(Color.Black).padding(20.dp).fillMaxSize(),verticalArrangement = Arrangement.Center){Row(verticalAlignment = Alignment.CenterVertically){Image(modifier= Modifier.width(160.dp).height(160.dp),painter= painterResource(id = robot.imageId),contentDescription = "${robot.description}")Text("${robot.name}",fontSize=36.sp,color=Color.White)}Text("${robot.description}",fontSize=24.sp,color=Color.Yellow)}}

为了在更好识别和处理这些不同的界面,定义密封类Screens来创建各自界面的实体对象:

/*** 定义可显示的界面* @property route String 导航线路名称* @property title String 界面标题* @constructor*/sealed class Screens(val route:String,val title:String){object HomePage:Screens("home","机器人列表")object RobotPage:Screens("robot","机器人详细信息")}

四、在不同的界面实现导航切换

为了实现导航,需要定义导航图,标明各个界面之间的导航方向,为此:

/*** Navigation graph screen* 定义导航图*/@Composablefun NavigationGraphScreen(){//获取导航控制器val navController = rememberNavController()NavHost(navController, startDestination = Screens.HomePage.route){composable(Screens.HomePage.route){RobotListScreen()}composable(Screens.RobotPage.route){RobotScreen()}}}

到目前位置,导航还未实现,这是因为在导航控制中缺乏导航控制器对象来处理导航动作,而且RobotItemView的对Image点击动作定义为空,所以是不可能实现导航;

因此做出如下修改:

(1)重新定义导航图

/*** Navigation graph screen* 定义导航图*/@Preview@Composablefun NavigationGraphScreen(){//获取导航控制器val navController = rememberNavController()NavHost(navController, startDestination = Screens.HomePage.route){composable(Screens.HomePage.route){RobotListScreen(navController)}composable(Screens.RobotPage.route){RobotScreen()}}}

在上述代码中,NavHost是导航图,是NavController的容器,指定了导航的起点路线,即Screens.HomePage.route

(2)修改RobotListScreen,增加导航控制器,使之具有界面导航的能力

/*** Robot list screen* 定义显示机器人滚动列表的界面*/@Composablefun RobotListScreen(navController:NavController){//增加导航控制器对象val robots = mutableListOf<Robot>()for(i in 1..20)robots.add(Robot(android.R.mipmap.sym_def_app_icon,"第${i}机器人","进入机器人世界"))var reverseLayout = falseBox(modifier= Modifier.background(Color.Black).fillMaxSize()){LazyColumn(state= rememberLazyListState(),verticalArrangement = if(!reverseLayout) Arrangement.Top else Arrangement.Bottom){items(robots){robot->RobotItemView(navController ,robot = robot) //将导航控制器对象传递给单项视图}}}}

(3)修改RobotItemView,将导航图中导航控制器对象作为参数传递给它,增加导航处理:

/*** 定义列表单项的视图* @param robot Robot*/@Composablefun RobotItemView(navController:NavController,robot:Robot){Column{Row(modifier= Modifier.fillMaxWidth().border(1.dp, Color.Black).clip(RoundedCornerShape(10.dp)).background(colorResource(id = R.color.teal_200)).padding(5.dp)){Image(modifier = Modifier.width(80.dp).height(80.dp).clip(shape = CircleShape).background(Color.Black).clickable {//增加导航处理//根据导航路线robot到Screens.RobotPage对应的RobotScreen定义的界面navController.navigate("robot")},painter = painterResource(id = robot.imageId),contentDescription = "机器人")Column{Text("${robot.name}",fontSize=16.sp,color=Color.Blue)Text("${robot.description}",fontSize=20.sp,color=Color.DarkGray)}}}}

到目前位置实现了从机器人滚动列表的选择指定单项的图标,跳转到下一个页面。但是由于每次跳转都是一个具有相同数据的界面,并不符合实际情况。实际情况需要完成的是从滚动列表中选择一个单项的图标,点击后进入这个“机器人”的详细信息的界面,因此需要传递相应的数据。

五、在导航中传递数据

1.传递基本类型的数据

假设从滚动列表跳转到机器人详细信息界面传递的是字符串,修改机器人列表单项视图界面,增加点击动作的发送数据的处理:

(1)修改RobotItemScreen函数,增加发送数据的处理

@Composablefun RobotItemView(navController:NavController,robot:Robot){Column{Row(modifier= Modifier.fillMaxWidth().border(1.dp, Color.Black).clip(RoundedCornerShape(10.dp)).background(colorResource(id = R.color.teal_200)).padding(5.dp)){Image(modifier = Modifier.width(80.dp).height(80.dp).clip(shape = CircleShape).background(Color.Black).clickable {//增加导航处理,发送方在导航路线中发送字符串数据navController.navigate("robot/${robot.toString()}")},painter = painterResource(id = robot.imageId),contentDescription = "机器人")Column{Text("${robot.name}",fontSize=16.sp,color=Color.Blue)Text("${robot.description}",fontSize=20.sp,color=Color.DarkGray)}}}}

(2)修改导航图

修改导航图,在导航图中为数据的接收方指定参数名称和参数类型,并修改导航路线为带参数的形式:

/*** Navigation graph screen* 定义导航图*/@Preview@Composablefun NavigationGraphScreen(){//获取导航控制器val navController = rememberNavController()NavHost(navController, startDestination = Screens.HomePage.route){//数据的发送方composable(Screens.HomePage.route){RobotListScreen(navController)}//数据的接收方composable(route=Screens.RobotPage.route+"/{robot}",//修改导航路线,增加要传递的参数名称arguments=listOf(navArgument("robot"){type= NavType.StringType}))//指定接收的参数和参数类型{val robotStr=it.arguments?.getString("robot")?:"没有任何信息,接收参数失败"RobotScreen(robotStr)}}}

(3)在接收方的机器人详细信息的界面是接收方,指定接收参数和参数类型的处理,代码如下所示:

/*** 定义机器人具体信息显示界面* @param robot Robot*/@Composablefun RobotScreen(robot:String){Column(modifier = Modifier.background(Color.Black).padding(20.dp).fillMaxSize(),verticalArrangement = Arrangement.Center){Row(verticalAlignment = Alignment.CenterVertically){Image(modifier= Modifier.width(160.dp).height(160.dp),painter= painterResource(id = android.R.mipmap.sym_def_app_icon),contentDescription = "${robot}")}Text("${robot}",fontSize=24.sp,color=Color.Yellow)}}

经过这样的处理,导航到机器人详细信息界面如下所示:

图3:接收传递的字符串数据

2.传递自定义类型的数据

在实际情况中,往往需要传递自定义类型的对象数据,如上述的Robot这个实现Parcelable接口的类型的对象时该怎么办?如果直接将这样的对象传递给下一个界面,形式如下:

(1)修改导航图

将导航图中的接收方的参数类型修改为Robot类型

@Preview@Composablefun NavigationGraphScreen(){//获取导航控制器val navController = rememberNavController()NavHost(navController, startDestination = Screens.HomePage.route){//数据的发送方composable(Screens.HomePage.route){RobotListScreen(navController)}//数据的接收方composable(route=Screens.RobotPage.route+"/{robot}",//修改导航路线,增加要传递的参数名称arguments=listOf(navArgument("robot"){//指定接收的参数和参数类型 type= NavType.inferFromValueType(Robot(R.mipmap.ic_launcher,"",""))})){val robot:Robot=it.arguments?.getParcelable("robot")?:Robot(android.R.mipmap.sym_def_app_icon,"测试","机器人信息获取失败")RobotScreen(robot)}}}

(2)修改接收数据方的界面

发送数据方的界面仍保持上述的内容,无需修改,只需要修改接收方的界面处理,将接收方RobotScreen函数接收的参数类型从字符串修改为Robot类型,GUI界面做出相应的处理即可,如下代码所示:

@Composablefun RobotScreen(robot:Robot){//修改参数类型为RobotColumn(modifier = Modifier.background(Color.Black).padding(20.dp).fillMaxSize(),verticalArrangement = Arrangement.Center){Row(verticalAlignment = Alignment.CenterVertically){Image(modifier= Modifier.width(160.dp).height(160.dp),painter= painterResource(id = robot.imageId),contentDescription = "${robot.description}")Text("${robot.name}",fontSize=36.sp,color=Color.White)}Text("${robot.description}",fontSize=24.sp,color=Color.Yellow)}}

会抛出java.lang.UnsupportedOperationException: Parcelables don’t support default values.

因为Parcelable类型的数据是不支持默认值。如果直接传递会抛出不支持操作的异常。因此需要其他方式来传递自定义类型的对象。

3. 利用Gson实现自定义数据的传递

其中一个解决方法就是在发送方将自定义类型对象的数据转换成JSON形式的字符串,然后在接收方将接收的字符串再转换成自定义类型的对象,从而达到传递数据的目的。在这里借助Gson框架来实现。

(1)增加Gson依赖

需要在模块的build.gradle中增加Gson框架的依赖,形式如下:

dependencies {implementation 'com.google.code.gson:gson:2.10'...}

(2)滚动列表的单项视图的定义

/*** 定义列表单项的视图* @param robot Robot*/@Composablefun RobotItemView(navController:NavController,robot:Robot){Column{Row(modifier= Modifier.fillMaxWidth().border(1.dp, Color.Black).clip(RoundedCornerShape(10.dp)).background(colorResource(id = R.color.teal_200)).padding(5.dp)){Image(modifier = Modifier.width(80.dp).height(80.dp).clip(shape = CircleShape).background(Color.Black).clickable {val robotStr = Gson().toJson(robot)//增加导航处理,发送方在导航路线中发送字符串数据navController.navigate("robot/${robotStr}")},painter = painterResource(id = robot.imageId),contentDescription = "机器人")Column{Text("${robot.name}",fontSize=16.sp,color=Color.Blue)Text("${robot.description}",fontSize=20.sp,color=Color.DarkGray)}}}}

(3)修改滚动列表界面的定义

/*** Robot list screen* 定义显示机器人滚动列表的界面*/@Composablefun RobotListScreen(navController:NavController){val robots = mutableListOf<Robot>()for(i in 1..20)robots.add(Robot(android.R.mipmap.sym_def_app_icon,"第${i}机器人","第${i}机器人进入机器人世界"))var reverseLayout = falseBox(modifier= Modifier.background(Color.Black).fillMaxSize()){LazyColumn(state= rememberLazyListState(),verticalArrangement = if(!reverseLayout) Arrangement.Top else Arrangement.Bottom){items(robots){robot->RobotItemView(navController,robot = robot)}}}}

代码没有发生变化

(4)接收数据方界面的定义

@Composablefun RobotScreen(robot:Robot){Column(modifier = Modifier.background(Color.Black).padding(20.dp).fillMaxSize(),verticalArrangement = Arrangement.Center){Row(verticalAlignment = Alignment.CenterVertically){Image(modifier= Modifier.width(160.dp).height(160.dp),painter= painterResource(id = robot.imageId),contentDescription = "${robot.description}")Text("${robot.name}",fontSize=36.sp,color=Color.White)}Text("${robot.description}",fontSize=24.sp,color=Color.Yellow)}}

(5)修改导航图

@Preview@Composablefun NavigationGraphScreen(){//获取导航控制器val navController = rememberNavController()NavHost(navController, startDestination = Screens.HomePage.route){//数据的发送方composable(Screens.HomePage.route){RobotListScreen(navController)}//数据的接收方composable(route=Screens.RobotPage.route+"/{robot}",//修改导航路线,增加要传递的参数名称arguments=listOf(navArgument("robot"){type= NavType.StringType}))//指定接收的参数和参数类型为字符串{val robotJsonStr=it.arguments?.getString("robot")?:"接收错误的参数"RobotScreen(Gson().fromJson(robotJsonStr,Robot::class.java))//将字符串转换成Robot对象}}}

在主活动中调用导航图的界面,代码如下:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Ch04_ComposeTheme {Surface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colors.background) {NavigationGraphScreen()}}}}}

最后的运行结果如下所示:

这时点击任意滚动列表单项图标,可以进入到指定的界面。

参考文献

使用Compose进行导航 https://developer./reference/androidx/navigation/NavHost?hl=zh-cn

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。