Android官方架构组件Navigation:Fragment管理框架

​一. 背景

从项目发展来看,随着业务迭代,我们 Fragment 之间的跳转越来越多:

Android 客户端组件化之后,由于团队基础的架构单个的业务组件规约是一个Activity 当中包含着多个Fragment。因此随着业务的迭代Fragment会越来越多,Fragment 之间的跳转,按照现有的方法,是采用封装的ReplaceFragment 方法,会有大量的跳转逻辑埋藏在代码里面令人头疼,也严重不符合单一职责。需要一种新的方案来进行Fragment 之间的管理。

从 Android 发展来看,简单的 Intent 无法满足界面跳转需求:

从一个界面跳转到另一界面,这是安卓开发的基础部分。过去,你可以使用 Intent 交互来完成此操作,在简单的情况下,例如单击按钮,这很容易。但如果你想做一些稍微复杂的事情呢?例如,像底部导航这样常见模式。你需要确保不仅你的底部导航视图可以真的导航,而且还要突出显示正确的按钮。而且它以统一的方式处理后台堆叠,这样用户就不会失去方向或迷茫。像这样的案例是新导航组件闪耀的地方。

Navigation 的出现:

该导航组件是一个可简化安卓导航的插件和工具。除了使底部导航等常见模式的设置更容易之外,该组件还处理后台堆栈,fragment 切换,参数传递,基于导航的动画和深层链接。重要的是,它会收集所有这些导航信息,并将其放在你应用程序的一个可视化的导航图。并支持 Deeplink 。

二.Navigation 的配置

1.在project的build.gradle 添加如下:

2.在app的build.gradle 添加如下:

三.Navigation 的简单跳转

1.添加导航图(类似iOS开发中的StoryBoard):

  • 击res目录,选择New > Android resource file
  • New Resource对话中输入文件名nav_graph_main.xml,选择Resource type为Navigation

点击OK后IDE会在navigation目录下生成nav_graph_main.xml文件

2.在Activity布局中指定Navigation的宿主(Host):

其中,fragment的name一定要是androidx.navigation.fragment.NavHostFragment,app:navGraph输入刚刚生成的导航图位置。

覆写onSupportNavigateUp()方法,如果app:defaultNavHost="true" 表示使用默认的导航host,自动覆盖Activity的back按钮,不用再覆写[AppCompatActivity.onSupportNavigateUp()]

3.自己创建多个fragmet.xml 如下

内容类似如下

4.Fragment.class 代码实现如下

5.Fragment 当中的跳转传参

传参通过Buddle,详见如下:

接收参数如下:

6.返回上前一个Fragment 方法

四、默认的参数跳转设置:

描述:可以用来保证参数不为空

在project的build.gradle当中添加

在app的build.gradle 当中添加

在nav_graph_main.xml 当中添加

argument 有三个属性 name、defaultValue 和 type,

  • name 就是名字到时会生成这个名字的 set 和 get 方法,
  • defaultValue 是默认值,
  • type 就是数据类型,有以下几种可以使用

五、设置跳转的动画

在nav_graph_main.xm当中配置

六、导航文件 nav_graph_main.xm 说明

id: 就像写布局的 id 那样需要给个 id 才能找到它

name: 指定哪个 Fragment 类名

tools:layout: fragment的layout

id :就是这个 action 的 id。

destination:是目的地,要跳转到哪里的。

还可以设置动画

点击下面的Design查看下:

七、Deeplink 的支持

在AndroidMainifest.xml 里面设置:

在 Navigation 里面设置:

八、类似 ViewPage 的支持

九、源码分析

我花了一些时间绘制了 Navigation的UML类图,我坚信,这种方式能帮助你我 更深刻的理解 Navigation的整体架构:

设计 NavHostFragment

NavHostFragment 应当有两个作用:

  • 作为Activity导航界面的载体
  • 管理并控制导航的行为

前者的作用我们已经说过了,我们通过在NavHostFragment的创建时,为它创建一个对应的FrameLayout作为 导航界面的载体


Group container, @Nullable Bundle savedInstanceState) {
	FrameLayout frameLayout = new FrameLayout(inflater.getContext());  
	frameLayout.setId(getId());  
	return frameLayout;  
 }

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable View

我们都知道代码设计应该遵循 单一职责原则,因此,我们应该将 管理并控制导航的行为 交给另外一个类,这个类的作用应该仅是 控制导航行为,因此我们命名为 NavController

Fragment理应持有这个NavController的实例,并将导航行为 委托 给它,这里我们将 NavController 的持有者抽象为一个 接口,以便于以后的拓展。

于是我们创造了 NavHost 接口,并让NavHostFragment实现了这个接口:

public interface NavHost {  
  NavController getNavController();
}

为了保证导航的 安全,NavHostFragment 在其 作用域 内,理应 有且仅有一个NavController 的实例

这里我们驻足一下,请注意API的设计,似乎 Navigation.findNavController(View),参数中传递任意一个 view的引用似乎都可以获取 NavController——如何保证 NavController 的局部单例呢?

事实上,findNavController(View)内部实现是通过 遍历 View树,直到找到最底部 NavHostFragment 中的NavController对象,并将其返回的:

private static NavController findViewNavController(@NonNull View view) {

        while (view != null) {

           NavController controller = getViewNavController(view);

            if (controller != null) {

                return controller;

            }

            ViewParent parent = view.getParent();

            view = parent instanceof View ? (View) parent : null; 
        }
        return null;
  }    

设计 NavController

站在 设计者 的角度,NavController 的职责是:

  • 1.对navigation资源文件夹下nav_graph.xml的 解析
  • 2.通过解析xml,获取所有 Destination(目标点)的 引用 或者 Class的引用
  • 3.记录当前栈中 Fragment的顺序
  • 3.管理控制 导航行为

NavController 持有了一个 NavInflater ,并通过 NavInflater 解析xml文件。

这之后,获取了所有 Destination(在本文中即Page1Fragment , Page2Fragment , Page3Fragment ) 的 Class对象,并通过反射的方式,实例化对应的 Destination,通过一个队列保存:

private NavInflater mInflater;  //NavInflater

private NavGraph mGraph;        //解析xml,得到NavGraph

private int mGraphId;           //xml对应的id,比如 nav_graph_main

//所有Destination的队列,用来处理回退栈
 private final Deque<NavDestination> mBackStack = new ArrayDeque<>();

这看起来没有任何问题,但是站在 设计者 的角度上,还略有不足,那就是,Navigation并非只为Fragment服务

先不去吐槽Google工程师的野心,因为现在我们就是他,从拓展性的角度考虑,Navigation是一个导航框架,今后可能 并非只为Fragment导航

我们应该为要将导航的 Destination 抽象出来,这个类叫做 NavDestination ——无论 Fragment 也好,Activity 也罢,只要实现了这个接口,对于NavController 来讲,他们都是 Destination(目标点)而已。

对于不同的 NavDestination 来讲,它们之间的导航方式是不同的,这完全有可能(比如Activity 和 Fragment),如何根据不同的 NavDestination 进行不同的 导航处理 呢?

NavDestination 和 Navigator

有同学说,我可以这样设计,通过 instanceof 关键字,对 NavDestination 的类型进行判断,并分别做出处理,比如这样:

if (destination instanceof Fragment) {

  //对应Fragment的导航

} else if (destination instanceof Activity) {

  //对应Activity的导航

}

这是OK的,但是不够优雅,Google的方式是通过抽象出一个类,这个类叫做 Navigator

public abstract class Navigator<D extends NavDestination> {

   //省略很多代码,包括部分抽象方法,这里仅阐述设计的思路! 
   //导航 
   public abstract void navigate(@NonNull D destination, @Nullable Bundle args, 

                                    @Nullable NavOptions navOptions);

   //实例化NavDestination(就是Fragment) 
  public abstract D createDestination();

    //后退导航 
   public abstract boolean popBackStack();
}

Navigator(导航者) 的职责很单纯:

  • 1.能够实例化对应的 NavDestination
  • 2.能够指定导航
  • 3.能够后退导航

你看,我的 NavController 获取了所有 NavDestination 的Class对象,但是我不负责它 如何实例化 ,也不负责 如何导航 ,也不负责

如何后退 ——我仅仅持有向上的引用,然后调用它的接口方法,它的实现我不关心。

FragmentNavigator为例,我们来看看它是如何执行的职责:

public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> {     //省略大量非关键代码,请以实际代码为主!   @Override   public boolean popBackStack() {        return mFragmentManager.popBackStackImmediate();    }​  @NonNull   @Override  public Destination createDestination() {    // 实际执行了好几层,但核心代码如下,通过反射实例化Fragment       Class<? extends Fragment> clazz = getFragmentClass();      return  clazz.newInstance();  }​  @Override  public void navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions)       // 实际上还是通过FragmentTransaction进行的跳转处理      final Fragment frag = destination.createFragment(args);      final FragmentTransaction ft = mFragmentManager.beginTransaction();      ft.replace(mContainerId, frag);      ft.commit();      mFragmentManager.executePendingTransactions();   }}


不同的 Navigator 对应不同的 NavDestinationFragmentNavigator 对应的是 FragmentNavigator.Destination,你可以把他理解为案例中的 Fragment ,有兴趣的朋友可以自己研究一下。

总结:

优点:减少Fragment 当中跳转相关的代码 ,更符合单一职责设计原理。缺点:多了一个xml。

Demo 地址 :

Navigation 跳转Demo:

https://github.com/YuriyPiKachu/NavigationDemo.git

Navigation 底部导航栏切换Demo :

https://github.com/YuriyPiKachu/NavigationAdvancedSample.git

Navigation 官网:

https://developer.android.google.cn/guide/navigation/

举报
评论 0