💡 一丶Navigation源码解析
谷歌推出Navigation主要是为了统一应用内页面跳转行为。本文主要是根据Navigation版本为2.1.0 的源码进行讲解。
'androidx.navigation:navigation-fragment:2.1.0'
'androidx.navigation:navigation-ui:2.1.0'
'androidx.navigation:navigation-fragment-ktx:2.1.0'
'androidx.navigation:navigation-ui-ktx:2.1.0'
Navigation
的使用很简单,在创建新项目的时候可以直接选择 Bottom
Navigation Activity
项目,这样默认就已经帮我们实现了相关页面逻辑。
Navigation
的源码也很简单,但是却涉及到很多的类,主要有以下几个:
-
Navigation
提供查找NavController
方法 -
NavHostFragment
用于承载导航的内容的容器 -
NavController
通过navigate
实现页面的跳转 -
Navigator
是一个abstract
,有四个主要实现类 -
NavDestination
导航节点 -
NavGraph
导航节点页面集合.
我们首先从NavHostFragment
入手查看,因为他是直接定义在我们的XML文件中的,我们直接查看器生命周期方法 onCreate
:
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
mNavController = new NavHostController(context); //1
mNavController.setLifecycleOwner(this);
....
onCreateNavController(mNavController);//2
....
}
注释1处 直接创建了NavHostController
并通过 findNavController
方法暴露给外部调用者。NavHostController
是继承自NavController
的。注释2处代码如下:
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
通过navController
获取NavigatorProvider
并向其中添加了两个Navigator
,分别为DialogFragmentNavigator
和FragmentNavigator
。另外在NavController
的构造方法中还添加了另外两个Navigator
,如下:
public NavController(@NonNull Context context) {
....
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}
他们都是Navigator
的实现类。分别对应于DialogFragment
、Fragment
和Activity
的页面跳转。大家可能对于NavGraphNavigator
一些好奇,它是用在什么地方的呢?其实我们在XML
中配置的navGraph
对应的navigation
跟节点文件中的 startDestination
就是通过NavGraphNavigator
来实现跳转的。这也是它目前唯一的用途。
各个Navigator
通过复写 navigate
方法来实现各自的跳转逻辑。这里重点强调下 FragmentNavigator
的实现逻辑:
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
....
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
....
ft.replace(mContainerId, frag); //1
....
}
最关键的一行代码就是注释1 处。他是通过 replace
来加载 Fragment
的 ,这不符合我们实际的开发逻辑。文章后续会讲解如何自定义 FragmentNavigator
来避免 Fragment
在切换的时候 生命周期的执行。
回到上文中的 navController
获取的 NavigatorProvider
其内部是维护了一个HashMap
来存储相关的Navigator
信息。通过获取到Navigator
的注解 Name
为key
和 Navigator
的 getClass
为 value
进行存储。
我们在回到上文中的 onCreate
方法:
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
....
if (mGraphId != 0) {
mNavController.setGraph(mGraphId);
} else {
....
if (graphId != 0) {
mNavController.setGraph(graphId, startDestinationArgs);
}
}
}
这里通过 mNavController
调用了 setGraph
。这里主要是为了解析我们的 XML
中配置的mobile_navigation
节点信息文件。会根据不同的节点来各自解析。
@NonNull
private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
@NonNull AttributeSet attrs, int graphResId)
throws XmlPullParserException, IOException {
Navigator navigator = mNavigatorProvider.getNavigator(parser.getName());
final NavDestination dest = navigator.createDestination();
dest.onInflate(mContext, attrs);
....
final String name = parser.getName();
if (TAG_ARGUMENT.equals(name)) { // argument 节点
inflateArgumentForDestination(res, dest, attrs, graphResId);
} else if (TAG_DEEP_LINK.equals(name)) { // deeplink 节点
inflateDeepLink(res, dest, attrs);
} else if (TAG_ACTION.equals(name)) { // action 节点
inflateAction(res, dest, attrs, parser, graphResId);
} else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) { // include 节点
final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavInclude);
final int id = a.getResourceId(R.styleable.NavInclude_graph, 0);
((NavGraph) dest).addDestination(inflate(id));
a.recycle();
} else if (dest instanceof NavGraph) { // NavGraph 节点
((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
}
}
return dest;
}
通过获取 NavInflater
来对其进行解析。解析后返回 NavGraph
,NavGraph
是继承自 NavDestination
的。里面主要是保存了所有解析出来的节点信息。
最后简单的总结下就是通过 NavHostFragment
获取到NavContorl
并存储了相关的Navigator
信息。通过各自的navigate
方法进行页面的跳转。通过setGraph
来解析配置的页面节点信息,并封装为NavGraph
对象。里面通过SparseArray
来存储 Destination
信息。
💡 二丶自定义Navigator
需要自定义自己的 Navigator
用于承载 Fragment
。主要的实现思路就是继承现有的 FragmentNavigator
并复写其 navigate
方法,将其中的 replace
方法 替换为 show
和 hide
方法 来完成 Fragment
的切换。
那么我们自定义的 Navigator
如何才能让系统识别呢?这也简单,只要给我们的 类加上注解 @Navigator.Name(value)
那么他就是一个 Navigator
了。最后通过上文中分析的思路 在将其加入到NavigatorProvider
中 即可。
具体的自定义Navigator
核心代码:
@Navigator.Name("fixFragment") //新的 Navigator 名称
class FixFragmentNavigator(context: Context, manager: FragmentManager, containerId: Int) :
FragmentNavigator(context, manager, containerId) {
override fun navigate(
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): NavDestination? {
....
//ft.replace(mContainerId, frag)
/**
* 1、先查询当前显示的fragment 不为空则将其hide
* 2、根据tag查询当前添加的fragment是否不为null,不为null则将其直接show
* 3、为null则通过instantiateFragment方法创建fragment实例
* 4、将创建的实例添加在事务中
*/
val fragment = mManager.primaryNavigationFragment //当前显示的fragment
if (fragment != null) {
ft.hide(fragment)
}
var frag: Fragment?
val tag = destination.id.toString()
frag = mManager.findFragmentByTag(tag)
if (frag != null) {
ft.show(frag)
} else {
frag = instantiateFragment(mContext, mManager, className, args)
frag.arguments = args
ft.add(mContainerId, frag, tag)
}
....
}
}
自定义完成好,还需要将 mobile_navigation
的节点中远 fragment
替换为 fixFragment
节点。并删除布局文件中NavHostFragment
节点的.
app:navGraph="@navigation/mobile_navigation"
信息,因为我们需要手动将 FixFragmentNavigator
和 NavControl
进行关联。
//添加自定义的FixFragmentNavigator
navController = Navigation.findNavController(this, R.id.nav_host_fragment)
val fragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val fragmentNavigator =
FixFragmentNavigator(this, supportFragmentManager, fragment!!.id)
navController.navigatorProvider.addNavigator(fragmentNavigator)
navController.setGraph(R.navigation.mobile_navigation)
这样就完成了自定义 Navigator
实现切换 Tab 的时候 Fragment
生命周期不会重新执行了。
转载自:
公众号:Android开发之旅
网友评论