?一. 背景
从项目发展来看,随着业务迭代,我们 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 对应不同的 NavDestination,FragmentNavigator 对应的是 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/
本文暂时没有评论,来添加一个吧(●'◡'●)