Android导航组件学习(三)

1,397 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

概述

经过前两篇笔记的学习,已经对导航组件有了更深入的了解,导航组件可以帮助我们简化页面的跳转逻辑,尤其是Fragment之间的跳转,使用导航图,一方面我们可以轻易的查看页面之间的连接关系,另一方面可以减少Fragment切换时的样板代码,我们可以把更多的时间放在具体的业务逻辑上。

同时我们也了解到:导航图是一种资源文件,一般情况下,我们倾向于一个Activity有它独立的一系列的导航图,这篇学习笔记中我们将会学习到对导航图进行管理。

嵌套导航图

在之前的学习中,导航图中包含的每一个页面(Fragment,Activity,DialogFragment)都是一个节点,这就导致应用功能增加之后,页面也会变多,从而导致导航图变得膨胀难以阅读。嵌套导航图可以帮助我们在一定程度上缓解这个问题。

嵌套导航图的作用对象应该是那些可以独立提取出来页面,这些页面可以作为独立的小模块来看待。之所以这样说,是因为嵌套图和根图(就是我们之前创建的那样的导航图)一样,必须要有标识为起始目的地的目的地,嵌套图之外的目的地如果想要访问嵌套图内的目的地,则必须首先跳转到嵌套图的起始目的地。

比如我们有一个购物的应用,下单付款是其中必要的流程,在这个流程中包含了订单的显示页面,订单支付页面,订单支付的结果页面等,此时我们会发现无论我们从什么地方发起付款,我们都必须要首先进入到订单的显示页面然后一步一步执行完成这个流程,因此我们就可以把这个流程中的三个页面作为一个嵌套图。下面的代码演示了这个操作:

    <fragment
            android:id="@+id/shop_home"
            android:name="com.zyf.navstudy.nav_design.shop.ShopHomeFragment"
            android:label="shopHome"
            tools:layout="@layout/fragment_shop_home"
            >
        <action android:id="@+id/to_show_order"
                app:destination="@id/shop_show_order"
                />
    </fragment>

    <fragment
            android:id="@+id/shop_show_order"
            android:name="com.zyf.navstudy.nav_design.shop.ShopShowOrderFragment"
            android:label="showOrder"
            tools:layout="@layout/fragment_shop_show_order"
            >
        <action android:id="@+id/to_pay"
                app:destination="@id/shop_order_pay"
                />
    </fragment>

    <fragment
            android:id="@+id/shop_order_pay"
            android:name="com.zyf.navstudy.nav_design.shop.ShopOrderPayFragment"
            android:label="payOrder"
            tools:layout="@layout/fragment_shop_order_pay"
            >
        <action android:id="@+id/to_check_pay_result"
                app:destination="@id/shop_order_pay_result"
                />
    </fragment>

    <fragment
            android:id="@+id/shop_order_pay_result"
            android:name="com.zyf.navstudy.nav_design.shop.ShopOrderPayResultFragment"
            android:label="show order pay result"
            tools:layout="@layout/fragment_shop_order_pay_result"
            >
    </fragment>

通过上面的导航图我们可以很明显地看出,我们设计了四个页面,分别是:首页,显示订单的页面,账单支付页面,查看账单支付结果页面,这几个页面的跳转逻辑也是一个跟一个的,很明显。

如上面所言,账单支付流程可以抽取成一个嵌套图,嵌套图和根图一样,都type都是navigation,并且也需要一个id和一个startDestination,如下就创建了一个嵌套图。

    <!--创建订单支付流程的嵌套图-->
    <navigation
            android:id="@+id/pay_order_process"
            app:startDestination="@id/shop_show_order"
            >
        <fragment
                android:id="@+id/shop_show_order"
                android:name="com.zyf.navstudy.nav_design.shop.ShopShowOrderFragment"
                android:label="showOrder"
                tools:layout="@layout/fragment_shop_show_order"
                >
            <action android:id="@+id/to_pay"
                    app:destination="@id/shop_order_pay"
                    />
        </fragment>
    </navigation>

这里就是将上面的第一个页面复制到了嵌套图中,将后面两个页面目的地均复制到这个嵌套图中,便组成了真正的嵌套图。

还有一个问题是在没有嵌套导航图之前,我们在首页直接跳转到订单显示的页面,但是在将订单流程设置成嵌套图之后,对于嵌套图外部的其它导航图来说,并不知道这个嵌套图中的目的地信息,因此外部想要跳转到这个嵌套图内部的页面,就只能将destination设置为嵌套图的id

就像我们之前说的,嵌套图内部是一个可以提取的流程,外部的导航图要访问嵌套图内部的目的地,就只能先连接到嵌套图的其实目的地。所以是否要将某一个功能作为嵌套图使用和实际的业务需求是密切相关的,必须要保证嵌套图内部的目的地是一系列可以被提取的流程。下面的代码修改了首页要连接到的目的地:

    <fragment
            android:id="@+id/shop_home"
            android:name="com.zyf.navstudy.nav_design.shop.ShopHomeFragment"
            android:label="shopHome"
            tools:layout="@layout/fragment_shop_home"
            >
        <action android:id="@+id/to_show_order"
                app:destination="@id/pay_order_process"
                />
    </fragment>

上面的代码最终实现的效果如下:

嵌套图跳转

通过引用其它导航图

我们可以在导航图中通过<include>标签引用其它导航图,这对于那些多模块的项目或者团队项目来说是非常好用的,下面的代码演示了使用<include>引用了另一个导航图资源文件中的目的地。

  1. 首先定义一个nav_include导航图资源,这里面只有一个Fragment:
<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_include"
        app:startDestination="@id/include_home"
        >

    <fragment
            android:id="@+id/include_home"
            android:name="com.zyf.navstudy.nav_design.include.IncludeHomeFragment"
            android:label="test include"
            tools:layout="@layout/fragment_include_home"
            />

</navigation>
  1. 接着在需要连接到这个导航图中目的地的其它导航图中使用include标签导入:
<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_design_shop"
        app:startDestination="@id/shop_home"
        >

    <include
            app:graph="@navigation/nav_include"
            />

    ......
</navigation>
  1. 最后在需要连接目的地的地方使用这个导航图:
        <action
                android:id="@+id/to_include"
                app:destination="@id/nav_include"
                />

上面的代码执行后的结果如下:

跳转到include导航图页面

需要注意的是: include导入其它导航图和嵌套导航图的本质是一样的。通过include导入的导航图,我们也只能连接到它的根目的地,也就是startDestination指定的目的地,不能直接连接到其它的目的地。

全局操作

有时候我的某一个页面可能需要在任意页面中都可以访问到,比如我们的登录页面,每当识别到用户没有登录并且接下来的操作需要登录的时候我们就需要跳转到登录页面,或者如果网络请求返回相应的错误码的时候就需要跳转到登录页面。这个时候我们就需要将这个目的地设置为全局操作。

设置全局操作很简单,我们只需要定义需要被连接到的目的地和一个action即可。

  1. 首先定义需要被连接到的目的地:
        <fragment
                android:id="@+id/global_destination"
                android:name="com.zyf.navstudy.nav_design.global.GlobalDestinationFragment"
                android:label="this is a global destination"
                tools:layout="@layout/fragment_global_destination"
                />
  1. 接着我们直接在navigation创建一个action,这个操作就是全局操作:
<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_design_shop"
        app:startDestination="@id/shop_home"
        >

    <action
            android:id="@+id/to_global_destination"
            app:destination="@id/global_destination"
            />
    
    ......
</navigation>
  1. 直接使用这个actionid就可以跳转:
        binding.root.setOnClickListener {
            findNavController().navigate(R.id.to_global_destination)
        }

上面的代码运行后的效果如下:

全局操作跳转

但是需要注意的是:这里的全局操作并不是真正意义上的全局操作,我们知道,每一个导航资源文件其实是和Activity绑定的,所以这里的全局操作也只限于这个导航资源文件绑定的Activity的,如果我们想要在另一个Activity中使用不属于它的全局操作,那么就会出现异常,如下所示:

java.lang.IllegalArgumentException: Navigation action/destination com.project.mystudyproject:id/to_global_destination cannot be found from the current destination Destination(com.project.mystudyproject:id/nav_second_fragment) label=second_fragment class=com.zyf.navstudy.nav_dialog.NavSecondFragment

所以这里所谓的全局操作,其实是针对单ActivityFragment项目的,这样的项目中全局操作那就是全局的,但是如果不是这样的项目那就不是全局的。