Navigation 是什么
官网的描述
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。
从官网的描述大概知道 Navigation是一套工具,一套在 Android Studio 中可以可视化管理界面的导航逻辑的工具
当然瞬间吸引人的还是官网放出的一张图
一目了然知道了项目的结构。
创建Navigation Graph (导航图)
带着学习的心态。按照Navigation 组件使用入门去创建属于自己的第一个导航图
添加依赖
笔者当前最新版本 2.3.2 开发者可以从官网英文版获取最新版本。中文版的文档总是会慢一点。
dependencies {
def nav_version = "2.3.2"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
创建导图xml文件
在需要创建的module中选择res,创建 Android Resource File
在弹出的窗口中。设置名称。选择 Navigation
即可在 res/navigation
下创建出一个开发者设置的导航图
将导航图添加到布局当中
创建一个平常的Activity并且为其创建一个xml.在其xml中添加 FragmentContainerView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
...>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
这里的 FragmentContainerView
是一个承载着内容的容器。记得之前看过官网 还是 使用 <fragment>
标签。不用过多纠结。主要看其中内部的几个参数
android:id
这个都知道干啥的。需要注意一点就是一定需要带上。如果忘记了添加id。会抛出错误提示你
Caused by: java.lang.IllegalStateException: FragmentContainerView must have an android:id to add Fragment androidx.navigation.fragment.NavHostFragment
android:name
属性包含 NavHost 实现的类名称。app:navGraph
属性将 NavHostFragment 与导航图相关联app:defaultNavHost="true"
确保您的NavHostFragment
会拦截系统返回按钮。请注意,只能有一个默认 NavHost。
充实导航图内容
为啥用充实
这个词呢。大家画过导航图或者其他类型的流程图都知道。流程图提供了各种各样的工具给你,让你在他们提供的画布上去绘制你需要的流程图。 Navigation
也是相同的道理。给开发者 提供了一个画布 (Navigation Graph (导航图))
需要如何使用就需要开发者自行去创作。创作的方式可以通过新建的方式去添加。也可以使用原有的东西。往画布 (Navigation Graph (导航图))
去添加。所以用了充实这个词。
首先先看一下 如果通过 Navigation Graph (导航图)
提供的快捷方式去添加。点击我们新建的导航图。选择 Splite
或者Design
模式都可以看到有一个添加的小图标。
点击小图标。即可看到 Create new desination
点击即可创建属于自己的 desination
即(目的地) 当然还可以直接选择之前已经创建好的。比如我自己创建的Fragment
例如我添加一个HomeFragment
到 (Navigation Graph (导航图))
xml字段含义
我们来看一下生成的xml. <navigation>
是导航图的根节点,android:id="@+id/nav_graph"
是我们创建的 xml 名称。在开发者创建好xml 以后这些都是自动的。看一下添加了一个 HomeFragment
发生了什么?
首先多了一个<fragment>
标签。在<fragment>
标签下 android:lable
字段包含该目的地的 XML 布局文件的名称。android:id
字段包含该目的地的 ID,它用于在代码中引用该目的地。这些也不需要太过关心。在创建的时候开发工具就已经帮我们创建好了。同时在Design
面板中也能看到这些信息
看到 <navigation>
标签下有一个 startDestination
属性。这个属性表示当前的导航图,会显示那个界面,Navigation
还很贴心的给我们加上一个小房子。
显示预览图
有小伙伴们发现。google 给出的导航图都能预览。自己创建的都不能显示。差评。不急哈。在<fragment>
标签下加上一个layout
字段就能够显示预览啦
普通跳转即传参
创建跳转行为
上面都是搭建导航图。下面我们学着如果进行界面之间的的跳转。在 Design
标签页中,将鼠标悬停在目的地的右侧,该目的地右侧上方会显示一个圆圈,点击您希望用户导航到的目的地,并将光标拖动到该目的地的上方,然后松开。这两个目的地之间生成的线条表示操作
完成这个操作以后。xml 中会在起点的fragment
下生成一个<action>
标签,其中 app:destination
表示目的地
切换到Design
模式下点击 之间的连接线。也能看到详细的信息。
无参跳转
Fragment.findNavController().navigate(ResId)
//点击事件
btnAction(R.id.btn_nav_ordinary) {
//普通跳转 Fragment.findNavController
findNavController().navigate(R.id.action_homeFragment_to_logInFragment)
}
View.findNavController().navigate(ResId)
//点击事件
btnAction(R.id.btn_nav_ordinary) {
//普通跳转 View.findNavController
it.findNavController().navigate(R.id.action_homeFragment_to_logInFragment)
}
Activity.findNavController().navigate(ResId)
//点击事件
btnAction(R.id.btn_nav_ordinary) {
//普通跳转 Activity.findNavController
findNavController().navigate(R.id.action_homeFragment_to_logInFragment)
}
其中的 resId 是上面我们创建的
<action>
标签中的id
带参跳转
不推荐的方式。不推荐的方式。不推荐的方式。推荐的方式可以参考 safe ages跳转传参
小节
//点击事件
btnAction(R.id.btn_nav_args) {
//普通使用 bundle 传递参数
val bundle = Bundle()
bundle.putString("user_name", "allens")
bundle.putInt("user_age", 16)
findNavController().navigate(R.id.action_homeFragment_to_logInFragment, bundle)
}
获取的时候和Fragment以前的方式一样 使用 arguments
去获取。
//普通接受
Log.i("allens-tag", "使用Bundle user_name:${arguments?.get("user_name")}")
Log.i("allens-tag", "使用Bundle user_age:${arguments?.get("user_age")}")
safe ages跳转传参
相比于上面的跳转传参的方式。safe ages
则更佳的安全可靠。本小节就学习其使用方式。以及分析为什么他更加安全可靠。
配置
safe ages
看一下如何使用。按照使用 Safe Args 传递安全的数据中说明的使用方法。先添加 plugin
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
最新版本查看英文版 ,中文版更新不及时的
然后添加插件到应用或模块的 build.gradle
文件中
apply plugin: "androidx.navigation.safeargs"
生成适用于仅 Kotlin 模块的 Kotlin 语言代码,请添加以下行
apply plugin: "androidx.navigation.safeargs.kotlin"
无参跳转
配置完成以后记得要clean
rebuild
一下。会生成 {module}/build/generated/source/navigation-args/{debug}/{packaged}/{Fragment}Dircetions
这样一个文件。其中的文件名后缀为Directions
然后使用插件为我们创建的 Directions
类
//点击事件
btnAction(R.id.btn_nav_ordinary) {
//safe args 跳转
val action = HomeFragmentDirections.actionHomeFragmentToLogInFragment()
findNavController().navigate(action)
}
有参跳转
在 Design
模式下,点击启示位置
(注意是启示位置)布局信息,看到他变成蓝色以后。看到右边面板上有 Arguments
点击加号即可创建需要传递的参数.支持的参数类型在官网也有说明.
创建了两个字段。name
age
对应的导航图中也可以看到其信息
其中 android:defaultValue
为默认值。android:name
为参数名称 app:argType
为参数类型,app:nullable
为标记是否可空
同样的,完成以后记得clean
rebuild
一下.插件会给我们生成一个 Args
结尾的类。
//传递参数
//点击事件
btnAction(R.id.btn_nav_ordinary) {
val bundle = HomeFragmentArgs("江海洋",22).toBundle()
findNavController().navigate(R.id.action_homeFragment_to_logInFragment, bundle)
}
//获取参数
//方式1:使用 fromBundle
val bundle = arguments
if (bundle != null) {
val homeArgs = HomeFragmentArgs.fromBundle(bundle)
Log.i("allens-tag", "使用 safe args user_name:${homeArgs.name}")
Log.i("allens-tag", "使用 safe args user_age:${homeArgs.age}")
}
//方式2: 使用委托的方式
val args by navArgs<HomeFragmentArgs>()
Log.i("allens-tag", "使用 safe args navArgs user_name:${args.name}")
Log.i("allens-tag", "使用 safe args navArgs user_age:${args.age}")
重点敲黑板
先回答为啥说是安全的呢?
因为如果让你手动去写 name
age
等 极有可能出现写错的情况,所以将这种放在了编译器里面。这样就不会出现手动写错的失误。
等等!!。findNavController().navigate(R.id.action_homeFragment_to_logInFragment, bundle)
你确定??? id 不会 写错嘛
eumm??? 打脸打的太快。
其实这里并不是无解。在官网的示例中 明显可以看到 插件为我们生成的是可以带参数的。而我们生成的却没有。
在这里。笔者经常尝试发现。原先的参数填写在 启示位置
为了做对比。我又在 目的地位置
创建一个参数 size
参数类型为String
rebuild
一下。如果我们预期的一样。给我们生成了一个 LogInFragmentArgs
,此时。目光放到 HomeFragmentDirections
这个我们原先生成的类中。会发现。变了!
此时就可以欢乐的使用
//跳转
val action = HomeFragmentDirections.actionHomeFragmentToLogInFragment("很大")
findNavController().navigate(action)
//接受参数
val bundle = arguments
if (bundle != null) {
//注意 现在已经是 LogInFragmentArgs 了
val args = LogInFragmentArgs.fromBundle(bundle)
Log.i("allens-tag", "使用 safe args size:${args.size}")
}
val args by navArgs<LogInFragmentArgs>()
Log.i("allens-tag", "使用 safe args navArgs size:${args.size}")
好了 现在就真的安全了。也不存在 id 可能会写错的问题了。 sage agrs
真的安全哈
Deep Links
深层链接是指将用户直接转到应用内特定目的地的链接 举个例子。微信的通知来了。点击一下通知就要跳转到聊天界面。
显式深层链接
依然是放上官网的链接 创建显式深层链接
btn_action_link.setOnClickListener {
val bundle = Bundle()
bundle.putString("link_info", "显式深层链接")
val pendingIntent = NavDeepLinkBuilder(this)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.errorFragment)
.setArguments(bundle)
.createPendingIntent()
//通知
val notificationManager =
getSystemService(NOTIFICATION_SERVICE) as (NotificationManager)
createNotificationChannel()
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("標題")
.setContentText("内容")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setSmallIcon(R.drawable.placeholder)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
notificationManager.notify(1, builder.build())
}
创建隐式深层链接
使用起来也比较简单,先创建一个深层链接
如需启用隐式深层链接,您还必须向应用的 manifest.xml 文件中添加内容。将一个 <nav-graph>
元素添加到指向现有导航图的 Activity
创建好以后的 导航图
//使用即可
btn_action_hint_link.setOnClickListener {
val id = "10001"
findNavController(R.id.nav_host_fragment).navigate("http://www.allens.com/users/${id}".toUri())
}
添加动画效果
在 在目的地之间添加共享元素过渡 一节中,给出了非常漂亮的截图
在 Design
面板上可以看到
对应的xml
其含义笔者也简单做了一个示意图。
Welcome 跳转到 Abort 界面
Abort 界面 回退到 Welcome 界面