原文:Jetpack Compose学习(11)——Navigation页面导航的使用 - Stars-One的杂货小窝

在Android原生的View开发中的,也是有Navigation,原生我之后可能再出篇教程,今天讲解的则是compose版本的Navigation组件的使用

本系列以往文章请查看此分类链接Jetpack compose学习

说明

在原生中,官方比较推荐使用单Activity+多Fragment模式来构建一个APP,而Fragment的切换需要使用FragmentManager来进行管理,比如add replace等方法,对于开发者来说,也是比较繁琐,于是官方在Jetpack组件中就是推出了Navigation的库,可以方便开发者快速去管理多Fragment的页面堆栈问题

而今天的主要针对compose,compose的架构也是一个Activity+多个可组合项,而如何去切换可组合项?

以往的做法就是定义一个选中下标,下标更改了,重新渲染一下页面,从而达到切换页面的效果

而有了Navigation,则是可以在使用层面更为优雅和规范,也是方便管理,不易出现奇奇怪怪的堆栈问题等

基本使用

这里我们以一个使用了NavigationBar组件的页面进行改造

PS:这里我是新版本的AS创建的项目,默认使用的是Material3的依赖包,所以底部导航栏组件就是NavigationBar,如果是Material2的依赖包,则是叫BottomNavigation

效果如下图所示:

就是实现了两个底部菜单,然后可以进行页面的切换,代码如下所示:

@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
fun MyApp() { var currentSelect by remember {
mutableStateOf(0)
} val menuData = listOf(
BottomItemData("首页", Icons.Filled.Home),
BottomItemData("设置", Icons.Filled.Settings)
) Scaffold(modifier = Modifier.fillMaxSize()
, bottomBar = {
NavigationBar() {
menuData.forEachIndexed { index, bottomItemData ->
NavigationBarItem(
selected = index == currentSelect,
onClick = {
currentSelect = index
},
icon = {
Icon(
imageVector = bottomItemData.icon,
contentDescription = "点击按钮"
)
},
label = {
Text(
text = (bottomItemData.label)
)
},
)
}
}
}
) { innerPadding ->
//下面第3步就是对此部分代码进行更改 //IDE强制要使用者innerPadding,这里就简单的打印一下
println(innerPadding)
if (currentSelect == 0) {
Text("首页")
}else{
Text("设置")
} }
}

从上面代码中,我们可以看到我们是通过currentSelect这个变量来进行页面的显示管理,实际上这里管理会比较繁琐,这个时候就得用到今天的主角Navigation

1.引入依赖

implementation "androidx.navigation:navigation-compose:2.5.3"

2.声明NavController

首先,就是使用rememberNavController获得NavController对象

//导航
val navController = rememberNavController()

PS: 之后的跳转和返回上一级就是使用此对象过来就行

3.使用NavHost,声明页面路由

在布局content参数的函数里使用下面这段代码:

NavHost(navController = navController, startDestination = "MainPage") {
//声明名为MainPage的页面路由
composable("MainPage"){
//页面路由对应的页面组件
MainPage()
}
composable("SettingPage"){
SettingPage()
}
}

上面是声明了两个页面,MainPage和SettingPage则是它两的路由名,后面页面跳转需要使用到

NavHost方法中,参数需要上一步的NavController对象,startDestination则是开始的页面为MainPage

简单的解释,就是可以把NavHost也看出一个页面组件,这个组件默认显示的是MainPage页面

源码如下,点击展开
@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
fun MyApp() {
var currentSelect by remember {
mutableStateOf(0)
} //导航
val navController = rememberNavController() val menuData = listOf(
BottomItemData("首页", Icons.Filled.Home),
BottomItemData("设置", Icons.Filled.Settings)
) Scaffold(modifier = Modifier.fillMaxSize()
, bottomBar = {
NavigationBar() {
menuData.forEachIndexed { index, bottomItemData ->
NavigationBarItem(
selected = index == currentSelect,
onClick = {
currentSelect = index
},
icon = {
Icon(
imageVector = bottomItemData.icon,
contentDescription = "点击按钮"
)
},
label = {
Text(
text = (bottomItemData.label)
)
},
)
}
}
}
) { innerPadding ->
//IDE强制要使用者innerPadding,这里就简单的打印一下
println(innerPadding) NavHost(navController = navController, startDestination = "MainPage") {
composable("MainPage"){
MainPage()
}
composable("SettingPage"){
SettingPage()
}
} }
} @Preview(showBackground = true)
@Composable
fun MainPage(){
Column() {
Text(text = "主页")
}
} @Preview(showBackground = true)
@Composable
fun SettingPage() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "设置页面")
}
}

3.页面跳转

下面则是两个重要的方法:

  • navController.navigate() 页面跳转
  • navController.popBackStack() 回上一级,相当于点击返回键

这里,我们结合NavigationBar实现点击底部按钮切换不同的页面

实现效果如下图所示:

代码如下图所示

PS:这里需要注意,如果使用了navController.navigate()来进行页面跳转,实际上就是和打开一个新的Activity类似,存在一个页面堆栈

比如说上图的效果,如果点返回键,则是回到上次的页面,而不会立即退出APP

而且,每次调用navigate()会创建一个新的页面对象

如果需要单一创建一个对象,可以这样写

navController.navigate("MainPage"){
launchSingleTop = true
}

之后不过怎么跳转,都会用的同一个页面对象

不过一般我们会是在页面之间跳转的,比如说要再MainPage页面跳转到SettingPage页面,要怎么写呢?

MainPage传递NavController的参数,之后页面中的按钮来可以使用此对象来完成页面跳转的功能

不过官方比较推荐我们传递一个函数参数,如下代码所示:

//方法需要传函数参数
@Composable
fun MainPage(toSettingPage:()->Unit) {
Column() {
Text(text = "主页")
Button(onClick = { toSettingPage.invoke() }) {
Text("跳转到设置页面")
}
}
} NavHost(navController = navController, startDestination = "MainPage") {
composable("MainPage"){
//这里也要同步修改
//下面的是kotlin中的lambda的简化写法,不明白的可以先去了解下
MainPage(){
navController.navigate("SettingPage")
}
}
composable("SettingPage"){
SettingPage()
}
}

如何传参

上面的基本使用,例子也是比较简单

一般情况下,我们可能跳转到新页面的时候需要传递数据,比如说从列表页到详情页,需要传递点击的item项的id,从而详情页可以拿到id去请求接口从而获得详情的数据并展示,这个时候该如何操作呢?

针对上面的代码,我们假设进入Setting页面,需要传递一个字符串的参数名为id,则有下面的步骤:

1.修改NavHost定义路由

NavHost(navController = navController, startDestination = "MainPage") {
composable("MainPage"){
MainPage(){
navController.navigate("SettingPage")
}
}
composable("SettingPage/{id}"){
//这里获取传参过来数据
val id = it.arguments?.getString("id")
//可以传入SettingPage中,然后在页面中使用数据,这里我就没写了
SettingPage()
}
}

这里上面,可以看到加了个{id}(有点类似Spring Boot里的接口写法),就是标明需要传参的意思

这里实际上还可以声明类型,默认不写的话,就是说id是String类型的

arguments这个变量实际上就是我们Intent中常用的Bundle,所以获取数值的是调用了getString()方法

设置参数的类型

如果想要id是整数的话,应该怎么做呢?

如下面代码所示:

composable("SettingPage/{id}", arguments = listOf(navArgument("id") {
type = NavType.IntType
})) {
//省略....
}

通过arguments参数来设置传参对应配置,接收的是一个列表,也很好理解,因为会有多个参数嘛

而list里的元素的类型,则是NamedNavArgument,官方提供navArgument()方法方便我们快速构造NamedNavArgument类型

我们可以看看navArgument方法的参数

navArgument(
name: String,
builder: NavArgumentBuilder.() -> Unit
)

name则是需要与我们路由上定义的参数名一致,而builder则是一个函数,我们可以写个函数来快速进行参数的配置,主要可以设置以下三个属性:

  • type 类型
  • defaultValue 默认值
  • nullable 是否可空

PS:改了参数类型,对应的取值调用的getXX等方法也要修改哦!

可选参数

上面的例子,实际上是必传的一个参数,如果需要可选参数,有以下规则:

  1. 可选参数必须使用查询参数语法?argName={argName} 来添加
  2. 可选参数必须具有 defaultValue nullable = true (将默认值设置为 null)

第二点就是上面提到过的两个属性,例如我们加多一个age的可选参数:

composable("SettingPage/{id}?age={age}", arguments = listOf(
navArgument("id") {
type = NavType.IntType
}, navArgument("age") {
type = NavType.IntType
defaultValue = 0
}
)) {
//省略...
}

2.调用传参

上面说了那么多,那么应该如何去调用呢?

NavHost(navController = navController, startDestination = "MainPage") {
composable("MainPage") {
MainPage() {
//跳转的时候传参
navController.navigate("SettingPage/11?age=18")
}
} composable("SettingPage/{id}?age={age}", arguments = listOf(
navArgument("id") {
type = NavType.IntType
defaultValue = 0
nullable = true
}, navArgument("age") {
type = NavType.IntType
defaultValue = 0
}
)) {
//这里获取传参过来数据
val id = it.arguments?.getInt("id")
Log.d("starsone", "MyApp: $id")
SettingPage()
}
}

由于上面,我们是将跳转封装在了函数,所以只需要调整MainPage路由里的函数即可navController.navigate("SettingPage/11?age=18")

当然,这里age是可以不传的,默认则是0

关于写法优化

在上面的例子的,我们涉及到的页面也是比较少,但是,在APP更新迭代之后,页面可以会成倍地增加

这个时候我们如果还是每个路由直接写死的一个字符串变量,那么万一哪天不小心改了,就会导致路由无法跳转了,针对这个问题,解决思路也比较简单,使用枚举或者常量来管理路由名就可

object Pages{
const val MainPage = "mainPage"
const val SettingPage = "settingPage"
} NavHost(navController = navController, startDestination = Pages.MainPage) {
composable(Pages.MainPage) {
MainPage() {
navController.navigate("${Pages.SettingPage}/11")
}
} composable("${Pages.SettingPage}/{id}?age={age}", arguments = listOf(
navArgument("id") {
type = NavType.IntType
defaultValue = 0
nullable = true
}, navArgument("age") {
type = NavType.IntType
defaultValue = 0
}
)) {
//这里获取传参过来数据
val id = it.arguments?.getString("id")
Log.d("starsone", "MyApp: $id")
SettingPage()
}
}

除此之外,如果NavHost里的composable多了起来,也不好方便管理是吧,我们可以按照业务逻辑来进行一个嵌套导航(嵌套导航结构)

在NavHost里使用navigation()来实现嵌套导航结构:

NavHost(navController, startDestination = "home") {
...
// 当导航到 login 路由时,自动找到 username 目标进行显示
navigation(startDestination = "username", route = "login") {
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
...
}

为了更好的复用,我们还可以将其抽取成一个方法(这里官方就是使用了扩展方法):

fun NavGraphBuilder.loginGraph(navController: NavController) {
navigation(startDestination = "username", route = "login") {
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
} NavHost(navController, startDestination = "home") {
...
// 当导航到 login 路由时,自动找到 username 目标进行显示
loginGraph()
...
}

与底部导航栏联用

这里以官方给出的示例,贴下代码并讲解

使用到了navController.currentBackStackEntryAsState()navBackStackEntry?.destination

您可以利用 NavController.currentBackStackEntryAsState() 方法从 NavHost 函数中获取 navController 状态,并与 BottomNavigation 组件共享此状态。这意味着 BottomNavigation 会自动拥有最新状态。

不过官方使用的是密封类里构造底部菜单数据,我这里则是使用了list直接构造,也差不了太多

@Composable
fun MyApp() {
var currentSelect by remember {
mutableStateOf(0)
} //导航
val navController = rememberNavController() val menuData = listOf(
BottomItemData(Pages.PAGE_MAIN,"首页", Icons.Filled.Home),
BottomItemData(Pages.PAGE_SETTING,"设置", Icons.Filled.Settings)
) Scaffold(modifier = Modifier.fillMaxSize()
, bottomBar = {
NavigationBar() {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination menuData.forEach { bottomItemData ->
NavigationBarItem(
//设置当前项是否为选中
selected = currentDestination?.hierarchy?.any { it.route == bottomItemData.route } == true,
icon = {
Icon(
imageVector = bottomItemData.icon,
contentDescription = "点击按钮"
)
},
label = {
Text(
text = (bottomItemData.label)
)
},
onClick = {
navController.navigate(bottomItemData.route) {
//使用此方法,可以避免生成一个重复的路由堆栈
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
//避免重复选择会创建一个新的页面副本
launchSingleTop = true
// Restore state when reselecting a previously selected item
//当重新选择之前已选择项目恢复页面状态
restoreState = true
}
}, )
}
}
}
) { innerPadding -> println(innerPadding)
NavHost(navController = navController, startDestination = Pages.PAGE_MAIN) {
composable(Pages.PAGE_MAIN) {
MainPage()
}
composable(Pages.PAGE_SETTING) {
SettingPage()
} }
}
} //页面的枚举类
object Pages{
const val PAGE_MAIN = "main"
const val PAGE_SETTING = "setting"
} //底部菜单的实体数据类,包含路由,文字和图标
data class BottomItemData(val route:String,val label: String, val icon: ImageVector) //两个页面 @Composable
fun MainPage() {
Surface() {
Text(text = "首页")
}
}
@Composable
fun SettingPage() {
Surface() {
Text(text = "设置")
}
}

深层链接

深层链接实际就是Intent-Filter的用法,这里还是比较少用,就不准备详细说明了,具体使用可以参考导航 - Jetpack Compose Docs

参考

Jetpack Compose学习(11)——Navigation页面导航的使用的更多相关文章

  1. Jetpack Compose学习(7)——MD样式架构组件Scaffold及导航底部菜单

    Jetpack Compose学习(7)--MD样式架构组件Scaffold及导航底部菜单 | Stars-One的杂货小窝 Compose给我们提供了一个Material Design样式的首页组件 ...

  2. Jetpack Compose学习(1)——从登录页开始入门

    原文地址:Jetpack Compose学习(1)--从登录页开始入门 | Stars-One的杂货小窝 Jetpack Compose UI在前几天出了1.0正式版,之前一直还在观望,终于是出了正式 ...

  3. Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用

    原文地址: Jetpack Compose学习(3)--图标(Icon) 按钮(Button) 输入框(TextField) 的使用 | Stars-One的杂货小窝 本篇分别对常用的组件:图标(Ic ...

  4. Jetpack Compose学习(2)——文本(Text)的使用

    原文: Jetpack Compose学习(2)--文本(Text)的使用 | Stars-One的杂货小窝 对于开发来说,文字最为基础的组件,我们先从这两个使用开始吧 本篇涉及到Kotlin和DSL ...

  5. Jetpack Compose学习(5)——从登录页美化开始学习布局组件使用

    原文:Jetpack Compose学习(5)--从登录页美化开始学习布局组件使用 | Stars-One的杂货小窝 本篇主要讲解常用的布局,会与原生Android的布局控件进行对比说明,请确保了解A ...

  6. Jetpack Compose学习(9)——Compose中的列表控件(LazyRow和LazyColumn)

    原文:Jetpack Compose学习(9)--Compose中的列表控件(LazyRow和LazyColumn) - Stars-One的杂货小窝 经过前面的学习,大致上已掌握了compose的基 ...

  7. Jetpack Compose学习(4)——Image(图片)使用及Coil图片异步加载库使用

    原文地址 Jetpack Compose学习(4)--Image(图片)使用及Coil图片异步加载库使用 | Stars-One的杂货小窝 本篇讲解下关于Image的使用及使用Coil开源库异步加载网 ...

  8. Jetpack Compose学习(6)——关于Modifier的妙用

    原文: Jetpack Compose学习(6)--关于Modifier的妙用 | Stars-One的杂货小窝 之前学习记录中也是陆陆续续地将常用的Modifier的方法穿插进去了,本期就来详细的讲 ...

  9. Jetpack Compose学习(8)——State及remeber

    原文地址: Jetpack Compose学习(8)--State状态及remeber关键字 - Stars-One的杂货小窝 之前我们使用TextField,使用到了两个关键字remember和mu ...

  10. WinPhone学习笔记(一)——页面导航与页面相关

    最近学一下Windows Phone(接下来简称“WinPhone”)的开发,在很久很久前稍探究一下WinPhone中对一些传感器的开发,那么现在就从头来学学WinPhone的开发.先从WinPhon ...

随机推荐

  1. 【大数据课程】高途课程实践-Day02:利用Hive SQL编写离线数仓实现可视化展示

    〇.概述 1.实现内容 使用Hive SQL编程,构造分层离线数仓 并可以通过Quick Bi进行展示 2.过程 (1)数据接⼊到ODS层 (2)进⾏ODS到DWD层数据开发 (3)进⾏ODS到DIM ...

  2. 【每日一题】【双端降序队列Deque】2021年12月28日-239. 滑动窗口最大值

    给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧.你只可以看到在滑动窗口内的 k 个数字.滑动窗口每次只向右移动一位. 返回滑动窗口中的最大值. 来源:力扣(L ...

  3. day37-文件上传和下载

    文件上传下载 1.基本介绍 在Web应用中,文件上传和下载是非常常见的功能 如果是传输大文件一般用专门的工具或者插件 文件上传和下载需要用到两个包:commons-fileupload.jar和com ...

  4. NGINX的配置和基本使用

    Linux NGINX NGINX:engine X ,2002年开发,分为社区版和商业版(nginx plus ) 社区版:分为主线版(开发版,奇数),稳定版(偶数) Nginx官网:http:// ...

  5. 想做长期的 AB 实验?快来看看这些坑你踩了没

    作者:江颢 1.什么是长期的 AB 实验 大部分情况下,我们做的 AB 实验都是短期的,一到两周或者一个月之内的,通过分析这段时期内测得的实验效应得出实验结论,并最终进行推广. 长期实验即运行时间达数 ...

  6. 如何查看计算机的CPU信息

    CPU-Z是一款家喻户晓的CPU检测软件,是检测CPU使用程度极高的一款软件.它支持的CPU种类相当全面,软件的启动速度及检测速度都很快.另外,它还能检测主板和内存的相关信息,其中就有我们常用的内存双 ...

  7. [深度学习] 基于切片辅助超推理库SAHI优化小目标识别

    对象检测是迄今为止计算机视觉中最重要的应用领域.然而,小物体的检测和大图像的推理仍然是实际使用中的主要问题,这是因为小目标物体有效特征少,覆盖范围少.小目标物体的定义通常有两种方式.一种是绝对尺度定义 ...

  8. [深度学习] tf.keras入门1-基本函数介绍

    目录 构建一个简单的模型 序贯(Sequential)模型 网络层的构造 模型训练和参数评价 模型训练 模型的训练 tf.data的数据集 模型评估和预测 基本模型的建立 网络层模型 模型子类函数构建 ...

  9. [python]《Python编程快速上手:让繁琐工作自动化》学习笔记6

    1. 发送电子邮件和短信笔记(第16章)(代码下载) 1.1 发送电子邮件 简单邮件传输协议(SMTP)是用于发送电子邮件的协议.SMTP 规定电子邮件应该如何格式化.加密.在邮件服务器之间传递,以及 ...

  10. S2-015 CVE-2013-2135, CVE-2013-2134

    漏洞名称 S2-015(CVE-2013-2135, CVE-2013-2134) 利用条件 Struts 2.0.0 - Struts 2.3.14.2 漏洞原理 原理一:一旦配置通配符*,访问 n ...