你们好,我叫小琪;android
本人16年毕业于中南林业科技大学软件工程专业,毕业后在教育行业作安卓开发,后来于19年10月加入37手游安卓团队;shell
目前主要负责国内发行安卓相关开发,同时兼顾内部几款App开发。 安全
navigation——入门篇(本章讲解)微信
navigation——进阶篇markdown
navigation——实战篇 (敬请期待...)app
在平常开发中,愈来愈多的会使用到一个activity嵌套多个fragment的场景,典型的例子就是app的首页,通常都会由一个activity+多个子tab组成,那对于Fragment的显示、隐藏等咱们一般都是经过FragmentManager进行管理,但这种方式很容易形成代码臃肿,难以维护。ide
而经过Jetpack的导航组件——Navigation,就能够很方便的管理各fragment之间的切换,让开发变得更简单。oop
一个包含全部导航相关信息的 XML 资源布局
一种特殊的Fragment,用于承载导航内容的容器post
管理应用导航的对象,实现Fragment之间的跳转等操做
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
复制代码
首先确保AndroidStudio为3.3以上
1.右键res,点击New -> Android Resource Directory
2.在出现的面板第二行Resource type 下拉列表中选择 Navigation,而后点击 OK
3.res目录下会多出一个navigation的资源目录,右键该目录,点击New -> Navigation Resource File,输入须要新建的资源文件名,这里命名nav_graph,点击ok,一个nav_graph.xml就建立好了。
新建好的nav_graph.xml切换到design模式下,点击2处的加号,选择Create new destination,便可快速建立新的Fragment,这里分别新建了FragmentA、FragmentB、FragmentC三个fragment
建好后,可经过手动配置页面之间的跳转关系,点击某个页面,右边会出现一个小圆点,拖曳小圆点指向跳转的页面,这里设置跳转的关系为FragmentA -> FragmentB -> FragmentC。
切换到Code栏,能够看到生成了以下代码
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph" app:startDestination="@id/fragmentA">
<fragment android:id="@+id/fragmentA" android:name="com.example.testnavigation.FragmentA" android:label="fragment_a" tools:layout="@layout/fragment_a" >
<action android:id="@+id/action_fragmentA_to_fragmentB2" app:destination="@id/fragmentB" />
</fragment>
<fragment android:id="@+id/fragmentB" android:name="com.example.testnavigation.FragmentB" android:label="fragment_b" tools:layout="@layout/fragment_b" >
<action android:id="@+id/action_fragmentB_to_fragmentC2" app:destination="@id/fragmentC" />
</fragment>
<fragment android:id="@+id/fragmentC" android:name="com.example.testnavigation.FragmentC" android:label="fragment_c" tools:layout="@layout/fragment_c" />
</navigation>
复制代码
在MainActivity的布局文件中配置NavHostFragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<fragment android:id="@+id/fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
复制代码
如今咱们运行程序,就能够正常跑起来了,而且看到了FragmentA展现的页面,这是由于MainActivity的布局文件中配置了NavHostFragment,而且给NavHostFragment指定了导航视图,而导航视图中经过startDestination指定了默认展现FragmentA。
上面说到三个fragment之间的跳转关系是FragmentA -> FragmentB -> FragmentC,而且已经能够展现了FragmentA,那怎么跳转到FragmentB呢,这就须要用到NavController 了
打开FragmentA类,给布局中的TextView定义一个点击事件
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val navController = Navigation.findNavController(it)
navController.navigate(R.id.action_fragmentA_to_fragmentB2)
}
}
复制代码
若是发现不能自动导入布局文件,大几率是要给app.build添加插件‘kotlin-android-extensions’
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
复制代码
AndroidStudio4.1之后改为了
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
复制代码
能够看到,经过navController管理fragment的跳转很是简单,首先获得navController对象,而后调用它的navigate方法,传入前面nav_graph中定义的action的id便可。
按一样的方法给FragmentB中的TextView也设置一个点击事件,使得点击时跳转到FragmentC
运行程序,FragmentA -> FragmentB -> FragmentC,此时按返回键,也是一个一个页面返回,若是把前面的app:defaultNavHost设置为false,按返回键后会发现直接返回到桌面了,如今能体会到app:defaultNavHost这个属性的含义了吧。
在编辑nav_graph的时候,action属性除了设置目标页外,还能够设置动画、页面间参数传递、fragment回退栈管理等
enterAnim: 跳转时的目标页面动画
exitAnim: 跳转时的原页面动画
popEnterAnim: 回退时的目标页面动画
popExitAnim:回退时的原页面动画
配置动画后会发现action多了四个动画相关的属性
<fragment android:id="@+id/fragmentA" android:name="com.example.testnavigation.FragmentA" android:label="fragment_a" tools:layout="@layout/fragment_a" >
<action android:id="@+id/action_fragmentA_to_fragmentB2" app:destination="@id/fragmentB" app:enterAnim="@anim/enter_in" app:exitAnim="@anim/enter_out" app:popEnterAnim="@anim/exit_in" app:popExitAnim="@anim/exit_out" />
</fragment>
复制代码
上面的例子中介绍fragment之间的跳转,固然也能够支持参数传递。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val navController = Navigation.findNavController(it)
val bundle = Bundle()
bundle.putString("key", "test")
navController.navigate(R.id.action_fragmentA_to_fragmentB2, bundle)
}
}
复制代码
参数传递推荐使用谷歌官方的safeArgs,safe args与传统传参方式相比,好处在于安全的参数类型,而且经过谷歌官方的支持,能很方便的进行参数传值。
在项目的根build.gradle下添加插件
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.1"
复制代码
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.1"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
复制代码
而后在app的build.gradle中引用 'androidx.navigation.safeargs.kotlin'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'androidx.navigation.safeargs.kotlin'
复制代码
AS4.1之后:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'androidx.navigation.safeargs.kotlin'
}
复制代码
添加完插件后,回到nav_graph,切到design模式,给目标页面添加须要接收的参数,这里须要在FragmentA跳转到FragmentB时传参数,因此给FragmentB设置参数,点击FragmentB,点击右侧面板的Arguments右侧的+,输入参数的key值,指定参数类型和默认值,便可快速添加参数
添加完后,rebuild一下工程,safeArgs会自动生成一些代码,在/build/generated/source/navigation-args目录下能够看到
safeArgs会根据nav_graph中的fragment标签生成对应的类,action标签会以“类名+Directions”命名,argument标签会以“类名+Args”命名。
使用safeArgs后,传递参数是这样的
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val navController = Navigation.findNavController(it)
//经过safeArgs传递参数
val navDestination = FragmentADirections.actionFragmentAToFragmentB2("test")
navController.navigate(navDestination)
// 普通方式传递参数
// val bundle = Bundle()
// bundle.putString("key", "test")
// navController.navigate(R.id.action_fragmentA_to_fragmentB2, bundle)
}
}
复制代码
接收参数是这样的
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.let {
val value = FragmentBArgs.fromBundle(it).key
.......
}
.......
}
复制代码
点击destination,右侧面板中还能够看到popUpTo、popUpToInclusive、launchSingleTop
例子:FragmentA -> FragmentB -> FragmentC -> FragmentA
设置FragmentC -> FragmentA 的action为popUpTo=FragmentA ,popUpToInclusive=false,那么栈内元素变化为
最后会发现须要按两次返回键才会回退到桌面
设置popUpToInclusive=true时,栈内元素变化为
此时只须要按一次返回键就回退到桌面了,从中能够体会到popUpTo和popUpToInclusive的含义了吧。
深度连接,就是能够直接跳转到某个页面。navigation建立深度连接能够经过显示和隐式两种方式
按以前的方式新建一个须要经过深度连接打开的目标页面FragmentDeepLink,
接下来为它建立一个deeplink
nav_graph.xml相应的在生成了以下代码
<fragment android:id="@+id/fragmentDeepLink" android:name="com.example.testnavigation.FragmentDeepLink" android:label="fragment_deep_link" tools:layout="@layout/fragment_deep_link">
<argument android:name="key" android:defaultValue="测试" app:argType="string" />
<deepLink android:id="@+id/deepLink" app:uri="www.deeplink.com/{id}" />
</fragment>
复制代码
显示深度连接
显示深层连接使用PendingIntent来导航到特定页面,好比点击通知栏,快速打开目标页面。
tv_deeplink.setOnClickListener {
//显示深度连接
val notificationManager = NotificationManagerCompat.from(requireContext())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel =
NotificationChannel(channelId, channelName, importance)
channel.description = "deeplink"
notificationManager.createNotificationChannel(channel)
}
val navController = Navigation.findNavController(it)
val deepLinkBuilder = navController.createDeepLink()
val bundle = Bundle()
bundle.putString("key", "deeplink")
val pendingIntent = deepLinkBuilder
//传入graph资源文件
.setGraph(R.navigation.nav_graph)
//传入参数
.setArguments(bundle)
//传入须要经过深度连接打开的目标页面
.setDestination(R.id.fragmentDeepLink)
.createPendingIntent()
val builder = NotificationCompat.Builder(requireContext(), channelId)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("测试deepLink")
.setContentText("哈哈哈")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
notificationManager.notify(1, builder.build())
}
复制代码
隐式深度连接
隐式连接是当用户点击某个连接的时候,经过URI跳转到某个页面,刚刚已经为nav_graph.xml中的FragmentDeepLink添加了
<deepLink app:uri="www.deeplink.com/{id}" />
复制代码
该uri没有声明是http仍是https,那么这两个都能匹配。大括号内的是传递的参数。
AndroidManifest.xml中给FragmentDeepLink所属的activity添加一个<nav-graph>
属性,这为MainActivity
<activity android:name=".MainActivity">
......
<nav-graph android:value="@navigation/nav_graph"/>
</activity>
复制代码
在build后,经过目录app - > build - > outputs - > apk - > debug - > app-debug.apk查看AndroidManifest.xml中的MainActivity节点下会多出以下代码
<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="http" />
<data android:scheme="https" />
<data android:host="www.deeplink.com" />
<data android:pathPrefix="/" />
</intent-filter>
复制代码
Navigation 组件会将 <nav-graph>
元素替换为生成的 <intent-filter>
元素来匹配深层连接。
咱们能够经过adb来测试隐式深层连接的效果,打开命令行输入
adb shell am start -a android.intent.action.VIEW -d "http://www.deeplink.com/1"
复制代码
在系统弹出的窗口中,选择本身的应用打开,就能跳转到目标页面了。
本篇是navigation的入门篇,主要介绍了navigation的基本使用,下篇将从源码角度,剖析navigation是如何作到页面之间跳转的。
过程当中有问题或者须要交流的同窗,能够扫描二维码加好友,而后进群进行问题和技术的交流等;