Jetpack之Navigation使用

5,037 阅读6分钟

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 界面

参考

【背上Jetpack之Navigation】想去哪就去哪,Android世界的指南针