在Compose中使用Navigation
Navigation是Jetpack组件中的一个成员,主要作用是用于页面间的导航跳转。在很早之前谷歌就在推单Activity多Fragment架构,而Navigation就是用于管理Fragment的跳转的,当时是使用xml来声明导航图进行导航的,再加上没有必要对项目进行太大的改造,就一直没有去学习其使用的方式。而随着Coompose的逐渐完善,谷歌也对Navigation进行了适应性开发,使其也能支持Compose中的页面跳转,因此Navigation就成了必须要学习的了。
引入Navigation
引入Navigation比较简单,直接添加依赖就行,注意要添加对应的Compose版本。截止到最新,已经是2.9.0版本了,使用该版本对CompileVersion和APG版本都有要求,引入时可以直接进行同步编译,然后根据错误提示升级对应的其他插件的版本。
1 | |
简单使用
使用起来和其他导航组件是一样的,都是先定义导航图,然后根据key去进行跳转。这里导航图是通过NavHost进行创建的,跳转是通过NavController去实现的。因此,我们需要在可组合函数的最顶层中定义出这两个成员,然后将NavController传递给每一个界面,这些界面就通过它来进行跳转。
首先要定义不同的导航路径,该路径就是每个页面的路径,就类似于Key的作用,用来标识每个界面。可以使用一个具体的类名来标识,也可以使用一个字符串来标识。一般我们使用字符串进行标识,因为使用类的话,需要为每个界面单独创建一个类,太过于浪费资源了。
1 | |
导航图通过NavHost创建,即将对应的路径与页面进行关联。
1 | |
以上就是定义导航图的方式,用起来还是比较简单的,后续只需要关注这几个界面即可,并且由于startDestination是Home,因此当我们启动这个MainActivity时,显示的界面就是HomeScreen界面。如果我们要跳转,可以通过navigation方法进行跳转,如下:
1 | |
跳转通过navController.navigate跳到指定的界面,然后通过navController.navigateUp返回。当然,按下返回键实际上也相当于执行了navigateUp,会进行返回。
到这里,我们其实已经掌握了最简单的使用方式了,即定义导航图,然后进行跳转和返回。但实际中我们肯定不会这么简单的,因为会涉及到参数的传递、ViewModel的使用、界面跳转的动销等等,接下来我们继续看下详细的功能。
NavHost
NavHost是用来创建导航图的,它本质上是个接口类,当然我们并不需要关注它的类型,我们直接使用对应的方法即可。在NavHost.kt中,提供了一个方法来创建导航图,与接口类的名称是一样的,方法名也是NavHost。我们需要关注的是它的方法参数。
navController导航控制器,与当前NavHost关联的控制器,当前host中定义的界面就是通过该控制器进行跳转返回等操作。startDestination起始的导航页,注意这里类型在不同的函数重载中可以为String也可以为KClass类型,具体要根据自己的定义导航的方式。modifier页面参数修改器,该修改器影响的是路由的整个界面。contentAlignment注释说是AnimatedContent的对其方式,但是AnimatedContent自己已经有这个参数了,它为啥还要用你提供的呢?route暂未发现有什么用,网上说是用于多个NavHost之间跳转,实测不行。enterTransition进入界面的切换动画、exitTransition原界面的消失动画、popEnterTransition返回时进入界面的动画、popExitTransition返回时原界面的消失动画。如果不指定的话,popEnter和enter的动画是一致的,popExit和exit的动画是一致的。举个例子:A界面跳转到B界面,此时A界面执行exit动画,B界面执行enter动画;然后从B界面返回到A界面时,B界面执行popExit动画,A界面执行popEnter动画。sizeTransform动画过程中的控制builder创建对应的界面
以上就是创建NavHost的参数,我们重点关注的就是起始页以及四个动效,从而控制我们页面的跳转动画。在最后一个参数builder中,我们需要创建对应的界面,通常我们使用composable方法来声明一个普通界面,通过dialog声明一个对话框界面。
navigation
在NavHost中,我们还可以使用navigation将一组composable整合起来,这样做可以方便我们进行模块划分。
1 | |
如上代码,在NavHost除了普通的composable界面外,还使用navigation将一组界面包含在了一块,即嵌套界面。注意我们可以从外层的界面跳转到嵌套内部的界面,但是不能从内部的界面跳到外部的界面。
1 | |
这种模式适合对某个特定功能封装的情况,即某个功能模块的界面都封装在一块。
composable
在NavHost定义界面时,使用composable方法定义一个界面,使用dialog方法定义一个对话框界面,实际上我们甚至可以在composable中不去定义对应的界面,而是直接跳转到其他的Activity,当然我们一般不这样用。
1 | |
稍微简化下可以看到它的定义中,有我们前面了解的那四个动画以及一个sizeTransfrom,前面在NavHost中定义的动画属于全局动画,也就是它所有的界面都会应用这四个动画,但如果想要与众不同的话,则需要在composable中设置单属于自己这个界面的动画。
arguments
除了这五个参数外,我们需要关注的就是route,这是当前界面的路由地址,通过该route就可以跳转到这个界面。然后另一个参数就是arguments,正常我们跳转界面都会携带参数,在Activity中我们使用Intent传递参数,在这里肯定不能直接使用Intent了,而是使用route+arguments来传递参数。
1 | |
可以看到在传递参数时,参数列表是已经定义在composable中的,主要集中在route中,表现形式类似于URL。其中可以将参数跟在主route后面,使用/的形式,然后参数使用占位符{}描述,如上面例子中的uid和uname。还有一种形式的参数是通过?后面进行拼接的参数,如sex1和age。
定义完占位符后,需要在arguments数组中描述占位符的类型,可以通过navArgument方法快速定义。然后描述类型使用type表示类型,nullable表示参数是否可空,defaultValue表示默认值。
需要注意一点的是:对于路径占位符(即跟随在/后的占位符),不论是否可空,在跳转时都是不可省略的,因此不需要声明默认值defaultValue。
1 | |
而对于 查询占位符(?后的占位符),如果将其设置为了可空的,则需要设置默认值,并且在跳转时参数可以省略不写。
1 | |
当参数名和占位符不一致时,查询需要查占位符,例如route=pageC?name={other},此时查询时需要以other为key来获取参数值。
另外注意的就是占位符描述可以不加,即我在route中有定义占位符,但是我在arguments中不去声明这个占位符的类型等信息也是可以的。
deepLinks
deepLinks定义的是url类型的匹配符,即从网页端直接跳转到当前界面来。用法和原来的Activity一样,只是原来是从外部跳转到对应的Activity,而现在只有一个Activity了,就会跳到当前的Activity后,再由Navigation拦截并跳转到对应的界面而已。
1 | |
其中参数的传递仍然是以占位符的形式进行传递的,接下来就是在AndroidManifest中添加对应的filter。
1 | |
然后就可以响应外部的界面了,我们可以在其他html界面中嵌入<a href="test://mypage/wang?age=20">点击跳转</a>超链接,当点击超链接的时候就可以跳转到我们对应的界面了。或者测试用adb命令也是一样的:
1 | |
到这里NavHost相关的基本上都已经了解了,接下来我们继续看navController。
NavController
NavController是用来控制跳转的,在创建NavHost的时候第一个参数就是它,后续需要把它传递到每个界面中,然后在对应的界面中通过它来跳转。同样的,我们也可以通过它来获取到Context以及对应的Activity。
1 | |
最简单的用法就是上面这种方法,注意在Navigation中也是存在任务栈的,和Activity的任务栈一样,当通过navigate()方法跳转时,会把跳转的界面入栈,此时栈内就有两个界面,一个是原来的界面,栈顶是新跳转的界面。如果要返回,可以通过navigateUp()方法出栈,或者直接按手机上的返回按钮,也是一样的逻辑。
也就是说,使用了Navigation后,每个composable界面和原来的Activity对应,界面的出栈入栈对应Activity的出栈入栈。而同样的,在每个composable获取的ViewModel也是一样会跟随界面的销毁而销毁。在composable界面中,通过方法viewModel()获取ViewModel实例。
1 | |
如上述实例的两个界面,每个界面都有他们自己对应的ViewModel,当从PageA跳转到PageDetail的时候,PageA实际上还是在任务栈中没有销毁的,因此PageViewModel并不会销毁。而从PageDetail返回到PageA的时候,由于PageDetail界面被销毁了,因此它对应的DetailViewModel也会被销毁掉,和Activity的表现是一致的。
另外,我们可以使用popBackStack()来弹出多个界面。
1 | |
对于带参数的popBackStack,第一个参数是目标界面,第二参数是否包含自己。例如当前的任务栈有由以下几个界面组成A->B->C->D,如果我们在D界面通过popBackStack(B, true),则会将DCB全部弹出,此时回到A界面,如果第二参数设置为false,表明不包含B,则只会弹出DC,此时回到B界面。
这是返回的操作退出任务栈,如果我们想在跳转的时候清除任务栈,例如登录成功后跳转到首页,此时就需要在跳转时就将登录界面弹出。
1 | |
由于我们可以轻易弹出界面,所以我们也可以来实现Activity的四种启动模式了。对于标准启动模式,我们什么都不需要做,默认就是标准启动模式。
对于singleTop模式,也是默认支持的,只需要我们在跳转时设置为singleTop即可。
1 | |
对于singleTask模式,我们需要手动弹出其他界面。
1 | |
但是对于singleInstance模式,似乎就无法完成了。
总结
至此,基本上已经了解了Navigation的使用方式了。实际上,在Compose中,Navigation是非常非常适合用来做路由的一个库,通过它我们可以以原来的Activity开发思想来进行设计,降低我们的学习曲线。关于Navigation的功能,总结下就是跳转返回、参数传递、任务栈管理。
