一、使用
二、源码解析
NavHostFragment的创建
官网上设置nav的xml文件
val finalHost = NavHostFragment.create(R.navigation.nav_graph_main);
supportFragmentManager.beginTransaction()
.replace(R.id.ll_fragment_navigation, finalHost)
.setPrimaryNavigationFragment(finalHost)
.commit();
或者重写
@Override
public boolean onSupportNavigateUp() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.my_nav_host_fragment);
return NavHostFragment.findNavController(fragment).navigateUp();
}
1、NavHostFragment.create方法
(1)初始化Bundle,并且将graphResId,startDestinationArgs存储在Bundle中。
(2)new NavHostFragment()返回NavHostFragment实例。
@NonNull
public static NavHostFragment create(@NavigationRes int graphResId,
@Nullable Bundle startDestinationArgs) {
Bundle b = null;
if (graphResId != 0) {
b = new Bundle();
b.putInt(KEY_GRAPH_ID, graphResId);
}
if (startDestinationArgs != null) {
if (b == null) {
b = new Bundle();
}
b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
}
final NavHostFragment result = new NavHostFragment();
if (b != null) {
result.setArguments(b);
}
return result;
}
然就在NavHostFragment 里有许多生命周期的方法会跟随fragment的方法进行触发
2、XML文件的解析:主要是解析布局文件的两个属性。defaultNavHost和navGraph,并且初始化全局变量NavHostFragment.onInflate方法 当Fragment以XML的方式静态加载时,最先会调用onInflate的方法
(调用时机:Fragment所关联的Activity在执行setContentView时)
将FragmentContainerView下的defaultNavHost和navGraph解析出来
@CallSuper
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
@Nullable Bundle savedInstanceState) {
super.onInflate(context, attrs, savedInstanceState);
final TypedArray navHost = context.obtainStyledAttributes(attrs,
androidx.navigation.R.styleable.NavHost);
final int graphId = navHost.getResourceId(
androidx.navigation.R.styleable.NavHost_navGraph, 0);
if (graphId != 0) {
mGraphId = graphId;
}
navHost.recycle();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
if (defaultHost) {
mDefaultNavHost = true;
}
a.recycle();
}
3、onCreateNavController方法中
先记住帮我们造了一个导航的提供者NavigatorProvider,他是一个导航器,保存各种导航,其中mNavigatorProvider是NavController中的全局变量,内部通过HashMap键值对的形式保存Navigator类。createFragmentNavigator方法,构建了FragmentNavigator对象,其中抽象类Navigator还有个重要的实现类ActivityNavigator和NavGraphNavigator。这个两个类的对象在NavController的构造方法中被添加。
其中Navigator类的作用是:能够实例化对应的NavDestination,并且能够实现导航功能,拥有自己的回退栈。
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
private NavigatorProvider mNavigatorProvider = new NavigatorProvider();
@NonNull
public NavigatorProvider getNavigatorProvider() {
return mNavigatorProvider;
}
创建的FragmentNavigator
@Deprecated
@NonNull
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(),
getContainerId());
}
4、接下来执行onCreate
无论是XML实现还是代码实现,都会执行Fragment的onCreate方法.
NavController在这里被创建,并且NavHostFragment中有一个NavController对象。
(1)初始化NavController,NavController为导航的控制类,核心类。
mNavController = new NavHostController(context);
(2)if (savedInstanceState != null) {开始恢复状态}
(3)if (mGraphId != 0) {设置导航图信息}
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
final Context context = requireContext();
//导航初始化
mNavController = new NavHostController(context);
mNavController.setLifecycleOwner(this);
mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
// Set the default state - this will be updated whenever
// onPrimaryNavigationFragmentChanged() is called
mNavController.enableOnBackPressed(
mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
mIsPrimaryBeforeOnCreate = null;
mNavController.setViewModelStore(getViewModelStore());
onCreateNavController(mNavController);
Bundle navState = null;
//恢复数据
if (savedInstanceState != null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
mDefaultNavHost = true;
getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
}
if (navState != null) {
// Navigation controller state overrides arguments
mNavController.restoreState(navState);
}
//id在xml里面加了。就要设置id进去
if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
} else {
// See if it was set by NavHostFragment.create()
final Bundle args = getArguments();
final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
final Bundle startDestinationArgs = args != null
? args.getBundle(KEY_START_DESTINATION_ARGS)
: null;
if (graphId != 0) {
mNavController.setGraph(graphId, startDestinationArgs);
}
}
// We purposefully run this last as this will trigger the onCreate() of
// child fragments, which may be relying on having the NavController already
// created and having its state restored by that point.
super.onCreate(savedInstanceState);
}
接下来研究controller
mNavController.setGraph(mGraphId)
解析刚才的xml文件,第二个参数是启动的是谁,xml里设置过
.setGraph()方法
构建NavGraph
在构建NavController的时候,我们还调用了NavController.setGraph(graphId)方法,该方法主要是构建NavGraph。调用getNavInflater方法创建NavInflater对象,用于解析navigation xml
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
NavInflater.inflate方法
根据传入的XML资源id构建NavGraph,NavGraph组成Fragment路由的导航地图,而NavDestination代表了导航的每一个目的地。在解析完NavDestination后,需要要求NavDestination为NavGraph,即NavGraph是NavDestination的子类。而且在NavGraph内部存储了NavDestination信息。
@SuppressLint("ResourceType")
@NonNull
public NavGraph inflate(@NavigationRes int graphResId) {
Resources res = mContext.getResources();
XmlResourceParser parser = res.getXml(graphResId);
final AttributeSet attrs = Xml.asAttributeSet(parser);
try {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Empty loop
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
String rootElement = parser.getName();
//拿到目的地
NavDestination destination = inflate(res, parser, attrs, graphResId);
if (!(destination instanceof NavGraph)) {
throw new IllegalArgumentException("Root element <" + rootElement + ">"
+ " did not inflate into a NavGraph");
}
return (NavGraph) destination;
} catch (Exception e) {
throw new RuntimeException("Exception inflating "
+ res.getResourceName(graphResId) + " line "
+ parser.getLineNumber(), e);
} finally {
parser.close();
}
}
上面的inflate方法内部会继续调用inflate方法。
(1)getNavigator方法获取都Navigator实例,该实例在构建NavController是被添加进去,这里获取的是FragmentNavigator对象。
(2)createDestination方法,会调用FragmentNavigator的createDestination构建Destination对象。
(3)onInflate方法,解析destination XML
(4)while循环内部通过递归构建导航图。
@CallSuper
public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
if (mGraph != null) {
// Pop everything from the old graph off the back stack
popBackStackInternal(mGraph.getId(), true);
}
mGraph = graph;
onGraphCreated(startDestinationArgs);
}
通过NavInflater类之后,解析了XML文件构建整个Graph之后。下面回到setGraph方法,在解析玩XML后会,回到NavHostFragment.setGraph方法。
(1)popBackStackInternal方法将回退栈中的信息全部出栈。
(2)调用onGraphCreated主要是显示一个导航Fragment视图。
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
......
if (mGraph != null && mBackStack.isEmpty()) {
boolean deepLinked = !mDeepLinkHandled && mActivity != null
&& handleDeepLink(mActivity.getIntent());
if (!deepLinked) {
// Navigate to the first destination in the graph
// if we haven't deep linked to a destination
//导航到第一个目的地,点进去,可以看到加载第一个页面的过程,ui就看到第一个页面
navigate(mGraph, startDestinationArgs, null, null);
}
} else {
dispatchOnDestinationChanged();
}
}
5、onCreateView
NavHostFragment.onCreateView方法
该NavHostFragment的视图就只有一个FragmentContainerView extends FrameLayout
containerView.setId(getContainerId());//这行主要用于以代码方式添加fragment
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
FragmentContainerView containerView = new FragmentContainerView(inflater.getContext());
// When added via XML, this has no effect (since this FragmentContainerView is given the ID
// automatically), but this ensures that the View exists as part of this Fragment's View
// hierarchy in cases where the NavHostFragment is added programmatically as is required
// for child fragment transactions
containerView.setId(getContainerId());
return containerView;
}
6、onViewCreated
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (!(view instanceof ViewGroup)) {
throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
}
Navigation.setViewNavController(view, mNavController);
// When added programmatically, we need to set the NavController on the parent - i.e.,
// the View that has the ID matching this NavHostFragment.
if (view.getParent() != null) {
mViewParent = (View) view.getParent();
if (mViewParent.getId() == getId()) {
Navigation.setViewNavController(mViewParent, mNavController);
}
}
}
至此、将第一页显示出来,然后相关的类初始化完成
调用
1.获取NavController
NavHostFragment.findNavController(fragment)
2.findNavController方法 该方法没什么实质性的代码,只要是调用了findViewNavController方法。
3.findViewNavController方法 通过view.tag查找NavController。内部调用了getViewNavController方法。
4.getViewNavController方法 通过获取view的Tag,获取NavController对象,这里的tag ID和setViewNavController都是nav_controller_view_tag。
Navigation.findNavController(view).navigate(R.id.action_page3);
@NonNull
public static NavController findNavController(@NonNull View view) {
NavController navController = findViewNavController(view);
if (navController == null) {
throw new IllegalStateException("View " + view + " does not have a NavController set");
}
return navController;
}
拿到controller,调用
1.在构建和获取到NavController对象以及NavGraph之后。,
下面是使用它来实现真正的导航了。下面从navigate开始分析。在navigate方法内部会查询到NavDestination,然后根据不同的Navigator实现页面导航。
navigate 方法
(1)如果回退栈为null返回NavGraph,不为null返回回退栈中的最后一项。
NavDestination currentNode = mBackStack.isEmpty() ? mGraph : mBackStack.getLast().getDestination();
(2)根据id,获取对应的NavAction。然后在通过NavAction获取目的地id。
final NavAction navAction = currentNode.getAction(resId);
destId = navAction.getDestinationId();
(4)利用目的地ID属性,通过findDestination方法,找到准备导航的目的地。
NavDestination node = findDestination(destId);
(5)开始导航
1.navigate(node, combinedArgs, navOptions, navigatorExtras);
2.NavDestination newDest = navigator.navigate(node, finalArgs,navOptions, navigatorExtras);
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
@Nullable Navigator.Extras navigatorExtras) {
NavDestination currentNode = mBackStack.isEmpty()
? mGraph
: mBackStack.getLast().getDestination();
if (currentNode == null) {
throw new IllegalStateException("no current navigation node");
}
@IdRes int destId = resId;
final NavAction navAction = currentNode.getAction(resId);
Bundle combinedArgs = null;
if (navAction != null) {
if (navOptions == null) {
navOptions = navAction.getNavOptions();
}
//拿到下一个目的地
destId = navAction.getDestinationId();
Bundle navActionArgs = navAction.getDefaultArguments();
if (navActionArgs != null) {
combinedArgs = new Bundle();
combinedArgs.putAll(navActionArgs);
}
}
...
//进行初始化时的跳转操作
navigate(node, combinedArgs, navOptions, navigatorExtras);
}
这里的操作有一个进栈的过程,所以会记录上一个进栈的过程
(1)NavHostFragment 作为导航载体,在Activity的layout文件里被引用(或者在代码中动态),并且持有导航控制类NavController引用。
(2)NavController 将导航任务委托给Navigator类,Navigator类有两个重要的子类FragmentNavigator和ActivityNavigator子类。NavController类持有NavInflater类引用。
(3)NavInflater 负责解析Navgation文件,负责构建NavGraph导航图。
(4)NavDestination 存有各个目的地信息,在FragmentNavigator和ActivityNavigator内部分别对应一个Destination类,该类继承NavDestination。
(5)在页面导航时,fragment的操作还是交由FragmentManager在操作,activity交由startActivity执行。
NavHostFragment 里使用navController初始化的时候使用解析器将整张图信息解析出来, 也即使minflterNavinflater 里持有NavGraph
用控制器访问目标位置NavDestination,调用navigate走到Navigator ,调用FragmentNew来显示。同时在栈内记录一次