导航组件概览 | MAD Skills

这是一个新的系列文章,我们称之为 “Modern Android Development 技巧”,简称为 “MAD Skills”。本系列文章致力于帮助开发者们打造更好的现代 Android 开发体验,敬请关注。今天为大家发布本系列文章中的第一篇: 导航 (Navigation) 组件概览。

概览

本文会简要概述导航组件,包括如何创建一个带有导航能力的、已启用导航的 UI 中有关包含层级的细节的新应用,以及对于一些主要 API 和导航组件工作原理的解释。

关于导航组件,网上已经有一些不错的内容资料:

撰写本章是为了本系列接下来的内容铺垫一些基础知识。

导航组件介绍

导航组件包括了相关 API 和 Android Studio 中的设计工具,其大大简化了您应用中导航流程的创建和编辑。以前没有导航组件的时候,应用中的导航任务是由我们手动编码实现的。您可能需要在每一个 UI 元素触发的导航动作代码中添加一个监听器,并编写代码使之启动一个 intent 来展示一个新 activity,或者切换到一个 Fragment。

您还需要在用户点击设备返回按钮和 ActionBar 的向上按钮时正确地处理返回和向上操作。有时候不同应用中处理这两个相关而又不完全相同的操作会产生一些不一致的结果。

有了导航组件后,我们可以使用其标准化的 API 以及 IDE 中的可视化工具,这些都可以帮助我们使整个导航流程更清晰、更简单以及更统一。您可以使用设计工具来创建导航目的地 (destination) 并定义导航路径,以及在您应用的导航图中切换目的地的相关操作 (action)。之后,您可以添加相关代码,使用户和应用的交互对应到合适的导航操作 (action) 上。

让我们来创建一个应用,并通过实际的工具和代码来体验一下导航组件。

导航模板

自 3.6 版本后,Android Studio 包含了一个非常有用的新功能,这就是将导航整合到创建新应用的模板中。这一功能并不是使用导航组件库所必须的,但它可以帮助集合所有必要的模块,从而极大地简化了创建新应用时使用导航的流程。

image

我们将使用这些模板之一的 Basic Activity 模板来创建一个新应用。除此之外的其他一些模板也自带导航,不过我们暂时先使用这个模板。

这个模板会帮我们创建一个包含导航组件基础结构的应用。我们还会得到两个目的地 (destination),以及定义了它们彼此之间导航路径的导航图。

image

IDE 加载完毕该应用之后,打开导航资源文件 nav_graph.xml 并在 Design 模式 (此外还有 Code 与 Split 模式) 下查看。您会看到当前应用导航图的样子。

您会发现两个目的地: FirstFragment 是那个被设置为初始页或者叫首页的目的地。SecondFragment 是另外那个我们可以导航到的目的地。

image

Basic Activity 模板可以创建两个目的地

点选这些目的地,您可以从右边的属性表单中查看它们的相关信息,比如下图中展示了这个目的地使用了 Fragment 类。

在之前图表的导航图中,您还可以发现两个目的地之间的箭头,它们定义了导航图中可能的导航操作 (action)。其中包括了从 FirstFragmentSecondFragment 的导航,以及从 SecondFragment 返回 FirstFragment 的导航。

操作 (action) 定义了可能的导航,但其不指定导航发生的时间,该逻辑存在于您的代码中。所以当用户点击某界面元素并需要触发导航的时候,您应该调用导航 API 使用其中一个操作来导航到图中的一个目的地。

操作还可以被用来定义传入目的地的参数,以及从源目的地和目的地进入退出的转场动画。我们会在之后的视频中介绍更多关于这些属性的内容,您也可以从 导航文档 - Navigation 组件使用入门 中了解更多关于它们的信息。

我们可以用导航工具来定义新的目的地,当我们还没有准备好目的地的 Fragment 类的时候,我们可以用占位符,也可以使用已存在的 Fragment 类。通过定义目的地以及它们对应的操作,您可以更直观地设计应用的所有界面跳转流程。

但是,代码呢?

到目前为止,我们一直在使用图形化工具开发导航,而像 Android Studio 中所有的资源文件一样,这些都是通过 XML 代码实现的,所以您也可以直接查看和编辑这些代码。如果在工具中切换到代码 (Code) 模式,您会发现如下的 XML 代码:

<?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/FirstFragment">
    <fragment
        android:id="@+id/FirstFragment"
        android:name="com.android.samples.navoverviewarticle.FirstFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/fragment_first">
        <action
            android:id="@+id/action_FirstFragment_to_SecondFragment"
            app:destination="@id/SecondFragment" />
    </fragment>
    <fragment
        android:id="@+id/SecondFragment"
        android:name="com.android.samples.navoverviewarticle.SecondFragment"
        android:label="@string/second_fragment_label"
        tools:layout="@layout/fragment_second">
        <action
            android:id="@+id/action_SecondFragment_to_FirstFragment"
            app:destination="@id/FirstFragment" />
    </fragment>
</navigation>

您会发现导航图的代码结构其实非常简单。其中作为根元素的 navigation,既定义了整个导航的结构,也包括了 起始目的地 (start destination) (或称之为 home destination)。在导航图中的每个目的地都是 fragment,每个目的地都包括 0 个或更多的操作 (action),操作定义了如何导航到导航图中的其他目的地。

Basic Activity 模板同时还创建了在两个目的地彼此之间导航的示例代码。举个例子,当用户点击 UI 中的按钮,FirstFragment 包含的如下代码会被触发:

override fun onViewCreated(view: View, savedInstanceState: Bundle?){
    super.onViewCreated(view, savedInstanceState)
    view.findViewById<Button>(R.id.button_first)
        .setOnClickListener {
         findNavController()
             .navigate(R.id.action_FirstFragment_to_SecondFragment)
    }
}

在这里调用 navigate() 方法,并传入在导航图中定义的 action_FirstFragment_to_SecondFragment 作为参数,会使应用导航到第二个目的地。

您可以运行应用并点击相关按钮 (或者返回按钮,该按钮会被自动插入导航返回事件) 来观察结果:

image

运行应用并使用 Next/Previous 按钮和返回按钮来导航

导航 UI 层次结构

image

我发现,观察 UI 中的各个部分在包含层级中的相互关系对于理解它们如何一起工作十分有帮助。为了查看这个部分,让我们来使用 Navigation Drawer Activity 模板创建另一个新工程。

当 Android Studio 加载应用完毕后,运行该应用您会看到如下图所示:

image

利用 Navigation Drawer Activity 模板创建的应用

和之前我们使用 Basic Activity 模板创建的应用不同,这个应用没有可以点击并导航到下一个目的地的按钮。取而代之的是在 DrawerLayout 中可以触发导航的菜单选项:

image

这一次,导航是由抽屉式导航栏中的菜单项触发的

当用户点击 DrawerLayout 中的菜单项时,应用会导航至和那些菜单项关联的目的地。这是因为导航组件自动绑定了菜单项和对应的目的地,所以您不必手动编写代码来创建这些链接。

让我们来看一下使这一切成功运转的 UI 层次结构。为了查看它,我们需要使用 Android Studio 中的 布局检查器 (Layout Inspector) 来剖析这个应用的 UI。

image

从工具 (Tools) 菜单启动布局检查器 (Layout Inspector)

布局检查器 (Layout Inspector) 让我们可以以图形化的方式查看整个应用的视图层次结构,同时我们也可以看到每一个容器及视图的属性。您应该可以看到如下图所示:

图中蓝色的矩形指示着当前被选中视图 (在上图示例中,DecorView 中的顶层 LinearLayout) 的边界。

其实我们本可以查看整个应用的层次结构 (而且我也十分鼓励大家这么做,这有助于可视化标准视图层级中所发生的事),但是我只想选择几个特定的视图来解释。首先,让我们看一下 ConstraintLayout 视图:

ConstraintLayout 容器是在 main_activity.xml 布局文件中被定义的,它包含了应用的实际内容 (但并不是所有内容,比如像 ActionBar 这种被模板创建好的元素)。在该容器中,我们可以看到 NavHostFragment 元素:

NavHostFragment 是使用导航组件时产生魔力的源泉,当用户在 fragment 之间导航的时候,它是 fragment 目的地被替换进出的容器。

另一个我想特别指出的是 NavigationView:

这个视图目前在左边屏幕外,它是一个 NavigationDrawer 并且其菜单选项被用来在目的地之间导航。该视图现在是不可见的,我们需要点击 ActionBar 菜单按钮来将它显示到屏幕上。

导航部件

我们已经在层级结构中查看了几个 UI 组件,以及它们彼此之间是如何关联的,接下来我想介绍一下几个重要部件,导航组件正是利用它们来在目的地之间实现导航。

一开始使用导航组件的时候,我发现有几个地方很让人迷惑,因为很多部件都使用 Navigation 和 Nav 这样的字眼,并且有些竟然比导航组件库本身存在的还要早。所以我觉得理解这些主要的部件是什么以及它们彼此的关系应该会很有帮助。

应用容器

为了图解这些部件是如何整合的,我会使用一个简化的应用容器的略图:

“工程师美术作品” 展示了应用内容的略图

我们会发现 Toolbar 在顶部,其中包括了 ActionBar 菜单按钮。然后应用内容存在于下方,其中包括了 NavHostFragment,而 NavHostFragment 包括了当前目的地的 UI。

NavHostFragment

正如我前面提到的,NavHostFragment 是导航时大量操作发生的地方。它是一个被导航组件用来替换进出目的地 fragment 的容器。当您在应用中导航到一个指定的 fragment 目的地时,NavHostFragment 会将其内容替换为那个指定的 fragment。

NavController

NavController 是一个被导航组件使用的内部部件,其在幕后起着决定性的作用。当用户在应用中导航的时候,NavController 在导航组件库中掌握着处理 NavHostFragment 替换进出目的地 fragment 的逻辑。

NavigationView

应用展示了 NavigationView (抽屉式导航栏) 覆盖在 activity 内容上方

接下来是 NavigationView,它是一个从左边划入的抽屉式导航栏。它在导航图中提供了一个可能目的地的菜单栏。NavigationView 其中一个很酷的特性是,您可以使用菜单项的 ID 自动地导航到对应菜单项关联的目的地,从而避免了手动创建基于菜单选择的重复代码。

有一点需要注意的是 NavigationView 存在于 NavHostFragment 容器之外,它本身并不是一个目的地,而只是一个指定应用导航目的地的途径。另外值得关注的一点是,早在其被导航组件整合进导航系统之前,这个 API 已经存在并被使用了很长一段时间。

NavigationUI

这个导航组件的部件被用来更新 NavHostFragment 以外的 UI。大部分的导航相关的图像更新发生在 NavHostFragment 内部,但是系统中仍然存在其他需要更新且不在容器内的部件,比如我们上面看到的抽屉式导航栏,以及类似 tab bar 的元素 (该组件可以被用来展示当前目的地信息)。

总结

这篇文章只是关于导航组件的一个快速概览,目的是为了让您体验如何创建一个可以使用导航功能的应用,以及看一下这种应用的大致结构。在未来的文章和视频中,针对如何同特定导航 API 进行交互,我会介绍更多的技术细节,比如导航到对话框目的地、使用 SafeArgs 以及处理深层链接。

更多信息

想了解更多关于导航组件的信息,请查阅 developer.android.google.cn 上的教程 Navigation 组件使用入门