1.Navigation是什么?
Navigation 是一个框架,用于在 Android 应用中的“目标”之间导航,该框架提供一致的 API,无论目标是作为 Fragment、Activity 还是其他组件实现。
自己的话:
Navigation是管理Fragment之间导航的组件库,特别在实现单个Activity多个Fragment的管理模式更加灵活
其是底部导航栏+Navigation
这里我们使用三个碎片所以先定义三个Fragment
package com.example.myapplication
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
class MainPage1Fragment :Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main_page1,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//点击事件跳转到第二个fragment
val btn = view.findViewById<Button>(R.id.btn)
btn.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page2)
}
}
}
package com.example.myapplication
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
class MainPage2Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main_page2,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val btn = view.findViewById<Button>(R.id.btn)
btn.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page1)
// Navigation.findNavController(view).navigateUp(); //返回上一个Fragment,
}
val btn2 = view.findViewById<Button>(R.id.btn2)
btn2.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page3)
}
}
}
package com.example.myapplication
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
class MainPage3Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main_page3,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val btn = view.findViewById<Button>(R.id.btn)
btn.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page2)
// 回退上一步
// Navigation.findNavController(view).navigateUp();
}
}
}
他们的布局如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#00BCD4">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainPage1Fragment"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#3F51B5"
android:layout_gravity="center" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转MainPage2Fragment"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/message"
android:layout_gravity="center"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#3F51B5">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#3F51B5"
android:text="MainPage2Fragment"
android:textSize="20sp"
android:textStyle="bold"
android:layout_gravity="center" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回MainPage1Fragment"
android:textAllCaps="false"
android:layout_gravity="center" />
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="前往MainPage3Fragment"
android:textAllCaps="false"
android:layout_gravity="center"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#9C27B0">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainPage3Fragment"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#3F51B5"
android:layout_gravity="center" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳到MainPage2Fragment"
android:textAllCaps="false"
android:layout_gravity="center" />
</LinearLayout>
布局很简单就是几个Button按钮
它的点击事件就是在我们Navigation工作绑定后进行页面的跳转,这个需要我们在MainActivity里进行绑定,先放在这
现在我们先做Navigation的布局
<?xml version="1.0" encoding="utf-8"?>
<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_graph_main"
app:startDestination="@id/page1Fragment">
<fragment
android:id="@+id/page1Fragment"
android:name="com.example.myapplication.MainPage1Fragment"
android:label="fragment_page1"
tools:layout="@layout/fragment_main_page1">
<!--
action:程序中使用id跳到destination对应的类
-->
<action
android:id="@+id/action_page2"
app:destination="@id/page2Fragment" />
</fragment>
<!-- TODO 同学们 这个是第二个 Fragment -->
<fragment
android:id="@+id/page2Fragment"
android:name="com.example.myapplication.MainPage2Fragment"
android:label="fragment_page2"
tools:layout="@layout/fragment_main_page2">
<action
android:id="@+id/action_page1"
app:destination="@id/page1Fragment" />
<action
android:id="@+id/action_page3"
app:destination="@id/page3Fragment" />
</fragment>
<!-- TODO 同学们 这个是第三个 Fragment -->
<!-- <navigation-->
<!-- android:id="@+id/nav_graph_page3"-->
<!-- app:startDestination="@id/page3Fragment">-->
<fragment
android:id="@+id/page3Fragment"
android:name="com.example.myapplication.MainPage3Fragment"
android:label="fragment_page3"
tools:layout="@layout/fragment_main_page3">
<action
android:id="@+id/action_page2"
app:destination="@id/page2Fragment" />
</fragment>
</navigation>
通过startDestination设置它的初始fragment,
通过action去绑定跳到destination对应的fragment
因为是底部导航栏tab所以我们需要去设置menu
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/page1Fragment"
android:icon="@drawable/ic_launcher_foreground"
android:title="第一页"/>
<item
android:id="@+id/page2Fragment"
android:icon="@drawable/ic_launcher_foreground"
android:title="第二页"/>
<item
android:id="@+id/page3Fragment"
android:icon="@drawable/ic_launcher_foreground"
android:title="第三页"/>
</menu>
现在我们看MainActivity和它的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
>
<!--
app:defaultNavHost="true"
拦截系统back键
-->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/my_nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="9"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_main"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemTextColor="#ff0000"
app:menu="@menu/menu"/>
</LinearLayout>
class MainActivity : AppCompatActivity() {
var bootomNavigationView :BottomNavigationView?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bootomNavigationView=findViewById(R.id.nav_view)
//让Navigation工作绑定
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?
val controller = navHostFragment!!.navController
//底部导航栏tab+Navigation
NavigationUI.setupWithNavController(bootomNavigationView!!,controller)
}
}
现在我们就可以通过点击按钮和点击底部导航栏将他们进行切换
效果如下
分析源码
1.官网上: MainActivity去绑定Navigation(实际上:源码理解 Fragment帖到MainActivity)
我们先记住这个概念,后面详细了解中会进行解答
我们在详细分析源码先把它大致的流程用图画出来,并进行讲解
我们先看下图进行讲解
第一条线
1.首先MainActivity里有个NavHostFragment
NavHostFragment有个NavHost接口,而NavHostFragment就是为了临时存放一个Fragment
最终是把他黏贴在Activity之上
2.有个关键点它会调用NavController里的NavInflater
3.而NavInflater通过NavGraph找到它的Navigation.xml,进行解析Navigation.xml布局
4.最终解析出来之后变成NavGraph对象,此对象拥有所有的Graph导航信息集
以后操作此对象,就可以操作导航信息了
第二条线
1.它通过NavController控制器 拿到NavAction去调用navigate()方法
构造出NavDestInation,控制我们跳转到那个Fragment,
2.而NavDestInation只是导航信息指挥对象,它是领导者并不进行直接的操作,
它去指挥Navigator进行干活,为什么这么做呢
就是用来隔离,做了一个中转,为了更好扩展性,这是单一职责原则。
因为Navigator有很多子类的
你会发现jetpack很多系统源码搞这么多中间层
是因为任何高耦合低扩展的问题,都可以来一个中间层解决,方便以后的维护
MainActivity
现在我们来看MainActivity里的代码
//让Navigation工作绑定
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?
val controller = navHostFragment!!.navController
//底部导航栏tab+Navigation
NavigationUI.setupWithNavController(bootomNavigationView!!,controller)
它是通过拿到NavHostFragment,再拿到控制器再将他们绑定,
那么按着我上面说的概念它是不是就是将NavHostFragmen帖到MainActivity上?
现在我们先跳进去看NavHostFragment的源码
可以看到它的构造函数是个空的,所以它最开始的create方法,注意这个create并不是生命周期的那个create
@NonNull
public static NavHostFragment create(@NavigationRes int graphResId) {
return create(graphResId, null);
}
这个graphResId是什么,它其实就是我们那个导航的ID
我们先进去查看,它是调用了下面的create
@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;
}
这里首先进行了Bundle的保存
然后再将这个 final NavHostFragment result = new NavHostFragment();
就是将自己实例化
onInflate
我们接下来要补充一个知识那就是fragment生命周期,我们都知道它是从onAttach()开始开始,但是有一种情况它在onAttach()前还有一个生命周期onInflate
1.onInflate(Context,AttributeSet,Bundle)只有硬编码在xml中的Fragment(即使用fragment标签)才会调用该方法,与自定义View十分类似,在实例化Xml布局时该方法会被调用
它的一些接收你可以查看下面文章
https://www.cnblogs.com/xiaowj/p/13913742.html
而我们其实也是在xml布局中NavHostFragment
@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();
}
看到这个TypedArray有过自定义View的人就会很熟悉就是在解析xml布局
而我们的XML布局其中如下
<androidx.fragment.app.FragmentContainerView
android:id="@+id/my_nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="9"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_main"
/>
先解析XMl布局
就开始走第二步
final int graphId = navHost.getResourceId( androidx.navigation.R.styleable.NavHost_navGraph, 0);
XMl布局拿到我们的navGraph,拿到了它的ID int graphId
在它的下面解析到了app:defaultNavHost="true"
这里就不在过多的介绍,
onCreateNavController
那么现在我们发现其中有个onCreateNavController,我们在MainActivity中有用到navController
而且在上面讲的流程中不管是第一条线还是第二条线都离不开他
我们来看他的代码
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
// 这里是通过导航暴露者,获取到 NavigationProvider 对象
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
其实 这里是通过导航暴露者,获取到 NavigationProvider 对象
NavigationProvider
我们查看NavigationProvider
@NonNull
public NavigatorProvider getNavigatorProvider() {
return mNavigatorProvider;
}
@SuppressLint("TypeParameterUnusedInFormals")
public class NavigatorProvider {
private static final HashMap<Class<?>, String> sAnnotationNames = new HashMap<>();
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private static boolean validateName(String name) {
return name != null && !name.isEmpty();
}
@NonNull
static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
String name = sAnnotationNames.get(navigatorClass);
if (name == null) {
Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
name = annotation != null ? annotation.value() : null;
if (!validateName(name)) {
throw new IllegalArgumentException("No @Navigator.Name annotation found for "
+ navigatorClass.getSimpleName());
}
sAnnotationNames.put(navigatorClass, name);
}
return name;
}
看到了吗它最顶部HashMap,我们知道在很多源码中map就是用来做缓存的作用,
所以看到这其实我们就可以知道这个HashMap就是为了把我们的三个fragment进行保存
那我们就明白了原来onCreateNavController就是为了初始化它的容器,再把它的暴露者初始化就行
但是其实会有疑惑为什么onCreateNavController写了两个getNavigatorProvider(),
因为我们出入在xml写入的方式,还可以通过代码写入,第二种createFragmentNavigator就是
如果你通过代码写入的方式,去存储fragment
onCreate
接下来我们再查看它的生命周期函数onCreate
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);
}
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);
}
它第一步就是实例化控制器NavHostController,设置lifecycler监听生命周期,
设置ViewModel保证数据在横竖屏切换不丢失,以及拦截back返回键
再就是我们上面提到的 onCreateNavController(mNavController);
而if判断就是为了拿到Bundle里面的值以及进行FragmentManager的操作
现在我们看重点
if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
}
就是拿到我们解析出来的navigation.xml导航图
将我们的布局设置进去
我们再进去查看,一层层的往下调用
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
这个startDestinationArgs就是在navigation.xml设置的我们第一个启动的fragment
我们去查看它的inflate
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();
}
}
XmlResourceParser就是安卓原生的解析方式,去解析布局
从开头解析到结尾最终返回我们的NavGraph,就是我们在上面提到的.最终解析出来之后变成NavGraph对象,
我们继续看下一层的
@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);
}
这个if判断,是进行弹栈,如果我们是首次打开首页不会进行这个操作,
当我们进行fragment的切换,按下返回键时候就会进行弹栈
而下面的nGraphCreated(startDestinationArgs);设置默认启动的frgment
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
if (mNavigatorStateToRestore != null) {
ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList(
KEY_NAVIGATOR_STATE_NAMES);
if (navigatorNames != null) {
for (String name : navigatorNames) {
Navigator<?> navigator = mNavigatorProvider.getNavigator(name);
Bundle bundle = mNavigatorStateToRestore.getBundle(name);
if (bundle != null) {
navigator.onRestoreState(bundle);
}
}
}
}
if (mBackStackToRestore != null) {
for (Parcelable parcelable : mBackStackToRestore) {
NavBackStackEntryState state = (NavBackStackEntryState) parcelable;
NavDestination node = findDestination(state.getDestinationId());
if (node == null) {
final String dest = NavDestination.getDisplayName(mContext,
state.getDestinationId());
throw new IllegalStateException("Restoring the Navigation back stack failed:"
+ " destination " + dest
+ " cannot be found from the current destination "
+ getCurrentDestination());
}
Bundle args = state.getArgs();
if (args != null) {
args.setClassLoader(mContext.getClassLoader());
}
NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
mLifecycleOwner, mViewModel,
state.getUUID(), state.getSavedState());
mBackStack.add(entry);
}
updateOnBackPressedCallbackEnabled();
mBackStackToRestore = null;
}
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
navigate(mGraph, startDestinationArgs, null, null);
}
} else {
dispatchOnDestinationChanged();
}
}
这里很复杂,我也没研究透,但是可以大概知道如果我们在xml布局里设置了startDestination设置初始化启动的fragment,以及对栈的管理
我们在看
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
navigate(mGraph, startDestinationArgs, null, null);
}
}
主要是执行navigate
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = false;
boolean launchSingleTop = false;
if (navOptions != null) {
if (navOptions.getPopUpTo() != -1) {
popped = popBackStackInternal(navOptions.getPopUpTo(),
navOptions.isPopUpToInclusive());
}
}
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
if (newDest != null) {
if (!(newDest instanceof FloatingWindow)) {
// We've successfully navigating to the new destination, which means
// we should pop any FloatingWindow destination off the back stack
// before updating the back stack with our new destination
//noinspection StatementWithEmptyBody
while (!mBackStack.isEmpty()
&& mBackStack.peekLast().getDestination() instanceof FloatingWindow
&& popBackStackInternal(
mBackStack.peekLast().getDestination().getId(), true)) {
// Keep popping
}
}
// When you navigate() to a NavGraph, we need to ensure that a new instance
// is always created vs reusing an existing copy of that destination
ArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>();
NavDestination destination = newDest;
if (node instanceof NavGraph) {
do {
NavGraph parent = destination.getParent();
if (parent != null) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, parent,
finalArgs, mLifecycleOwner, mViewModel);
hierarchy.addFirst(entry);
// Pop any orphaned copy of that navigation graph off the back stack
if (!mBackStack.isEmpty()
&& mBackStack.getLast().getDestination() == parent) {
popBackStackInternal(parent.getId(), true);
}
}
destination = parent;
} while (destination != null && destination != node);
}
// Now collect the set of all intermediate NavGraphs that need to be put onto
// the back stack
destination = hierarchy.isEmpty()
? newDest
: hierarchy.getFirst().getDestination();
while (destination != null && findDestination(destination.getId()) == null) {
NavGraph parent = destination.getParent();
if (parent != null) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs,
mLifecycleOwner, mViewModel);
hierarchy.addFirst(entry);
}
destination = parent;
}
NavDestination overlappingDestination = hierarchy.isEmpty()
? newDest
: hierarchy.getLast().getDestination();
// Pop any orphaned navigation graphs that don't connect to the new destinations
//noinspection StatementWithEmptyBody
while (!mBackStack.isEmpty()
&& mBackStack.getLast().getDestination() instanceof NavGraph
&& ((NavGraph) mBackStack.getLast().getDestination()).findNode(
overlappingDestination.getId(), false) == null
&& popBackStackInternal(mBackStack.getLast().getDestination().getId(), true)) {
// Keep popping
}
mBackStack.addAll(hierarchy);
// The mGraph should always be on the back stack after you navigate()
if (mBackStack.isEmpty() || mBackStack.getFirst().getDestination() != mGraph) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs,
mLifecycleOwner, mViewModel);
mBackStack.addFirst(entry);
}
// And finally, add the new destination with its default args
NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
mBackStack.add(newBackStackEntry);
} else if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
launchSingleTop = true;
NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast();
if (singleTopBackStackEntry != null) {
singleTopBackStackEntry.replaceArguments(finalArgs);
}
}
updateOnBackPressedCallbackEnabled();
if (popped || newDest != null || launchSingleTop) {
dispatchOnDestinationChanged();
}
}
我们重点看这段
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
就是为了拿到我们设置的startDestination,找到我们的第一个fragment,也是我们在fragment用到的
我们查看我们之前创建的Fragment
class MainPage1Fragment :Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main_page1,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//点击事件跳转到第二个fragment
val btn = view.findViewById<Button>(R.id.btn)
btn.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page2)
}
}
}
我们可以看到要跳转显示的fragment也是调用了navigate
在进去查看navigate,就是我们用到的同一个方法
这就是我上面说的
NavDestInation只是导航信息指挥对象,它是领导者并不进行直接的操作,
它去指挥Navigator进行干活
Navigator
我们去查看Navigator‘
public abstract class Navigator<D extends NavDestination> {
/**
* This annotation should be added to each Navigator subclass to denote the default name used
* to register the Navigator with a {@link NavigatorProvider}.
*
* @see NavigatorProvider#addNavigator(Navigator)
* @see NavigatorProvider#getNavigator(Class)
*/
@Retention(RUNTIME)
@Target({TYPE})
@SuppressWarnings("UnknownNullness") // TODO https://issuetracker.google.com/issues/112185120
public @interface Name {
String value();
}
/**
* Construct a new NavDestination associated with this Navigator.
*
* <p>Any initialization of the destination should be done in the destination's constructor as
* it is not guaranteed that every destination will be created through this method.</p>
* @return a new NavDestination
*/
@NonNull
public abstract D createDestination();
/**
* Navigate to a destination.
*
* <p>Requests navigation to a given destination associated with this navigator in
* the navigation graph. This method generally should not be called directly;
* {@link NavController} will delegate to it when appropriate.</p>
*
* @param destination destination node to navigate to
* @param args arguments to use for navigation
* @param navOptions additional options for navigation
* @param navigatorExtras extras unique to your Navigator.
* @return The NavDestination that should be added to the back stack or null if
* no change was made to the back stack (i.e., in cases of single top operations
* where the destination is already on top of the back stack).
*/
@Nullable
public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);
/**
* Attempt to pop this navigator's back stack, performing the appropriate navigation.
*
* <p>Implementations should return {@code true} if navigation
* was successful. Implementations should return {@code false} if navigation could not
* be performed, for example if the navigator's back stack was empty.</p>
*
* @return {@code true} if pop was successful
*/
public abstract boolean popBackStack();
/**
* Called to ask for a {@link Bundle} representing the Navigator's state. This will be
* restored in {@link #onRestoreState(Bundle)}.
*/
@Nullable
public Bundle onSaveState() {
return null;
}
/**
* Restore any state previously saved in {@link #onSaveState()}. This will be called before
* any calls to {@link #navigate(NavDestination, Bundle, NavOptions, Navigator.Extras)} or
* {@link #popBackStack()}.
* <p>
* Calls to {@link #createDestination()} should not be dependent on any state restored here as
* {@link #createDestination()} can be called before the state is restored.
*
* @param savedState The state previously saved
*/
public void onRestoreState(@NonNull Bundle savedState) {
}
/**
* Interface indicating that this class should be passed to its respective
* {@link Navigator} to enable Navigator specific behavior.
*/
public interface Extras {
}
}
它就是个抽象类,我们可以看它有6个子类继承了它,我们比较常用的Fragment
FragmentNavigator
所以我们去看FragmentNavigator
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
这里就是把我们千辛万苦保存的startDestination通过反射去找到这个fragment
再将他交给FragmentManager进行管理
再下面就是它fragment的进出动画
重点是它如何进行替换的,
ft.replace(mContainerId, frag);
这里的mContainerId就是我们xml布局里的NavHostFragment
就是将我们在mainactivity里的临时NavHostFragment与我们要显示的fragment进行替换
<androidx.fragment.app.FragmentContainerView
android:id="@+id/my_nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="9"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_main"
/>
为什么这么说呢,因为我们在NavHostFragment里面有个createFragmentNavigator()方法
@Deprecated
@NonNull
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(),
getContainerId());
}
private int getContainerId() {
int id = getId();
if (id != 0 && id != View.NO_ID) {
return id;
}
// Fallback to using our own ID if this Fragment wasn't added via
// add(containerViewId, Fragment)
return R.id.nav_host_fragment_container;
}
getContainerId()就是把我们的NavHostFragment的id返回
10 小结
小结:
第一步:解析所有的目的地
第二步:放到一个HashMap里面全部保存好
第三步:获取第一个目的地Fragment
第四步:初始化目的地的所有信息
第五步:显示第一个目的地Fragment 画面
额外补充
额外补充
NavHostFragment.onViwCreated
这里有一个 Fragment的生命周期函数,忘记说了
@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); // 会把我们的 mNavController 加入到 Navigation里面去
// 说白了,如果是代码的方式去写的话,并非是xml的写法的话,就做下面的代码,但是 大部分都是 xml的形式
// 当以编程方式添加时,我们需要在父级上设置 NavController - 即具有与此 NavHostFragment 匹配的 ID 的视图
// 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);
}
}
}
>>> 下面是使用navigation角度上分析源码
1 MainPage1Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val btn = view.findViewById<Button>(R.id.btn)
btn.setOnClickListener { view ->
Navigation.findNavController(view).navigate(R.id.action_page2) // 在这里开始导航
}
}
2 NavController.navigation
public void navigate(@IdRes int resId) {
navigate(resId, null);
}
public void navigate(@IdRes int resId, @Nullable Bundle args) {
navigate(resId, args, null);
}
public void navigate(@IdRes int resId, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
navigate(resId, args, navOptions, null);
}
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
@Nullable Navigator.Extras navigatorExtras) {
// 目的 是在 Graph里面去寻找 当前节点的Fragment 为 目的地
NavDestination currentNode = mBackStack.isEmpty() // 先去看看栈有没有
? mGraph
: mBackStack.getLast().getDestination(); // 如果栈没有,就取最后一个
if (currentNode == null) {
throw new IllegalStateException("no current navigation node");
}
@IdRes int destId = resId;
// 获取节点后,就要去获取 action 导航动作,只有action才能通过resId 获取下一个要导航的目标id
final NavAction navAction = currentNode.getAction(resId);
Bundle combinedArgs = null;
if (navAction != null) {
if (navOptions == null) {
navOptions = navAction.getNavOptions();
}
destId = navAction.getDestinationId(); // 获取下一个目的地的 id号信息等
Bundle navActionArgs = navAction.getDefaultArguments();
if (navActionArgs != null) {
combinedArgs = new Bundle();
combinedArgs.putAll(navActionArgs);
}
}
if (args != null) {
if (combinedArgs == null) {
combinedArgs = new Bundle();
}
combinedArgs.putAll(args);
}
if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != -1) {
popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
return;
}
if (destId == 0) {
throw new IllegalArgumentException("Destination id == 0 can only be used"
+ " in conjunction with a valid navOptions.popUpTo");
}
// 下面代码只是一个检查健壮性而已,先不管
NavDestination node = findDestination(destId);
if (node == null) {
final String dest = NavDestination.getDisplayName(mContext, destId);
if (navAction != null) {
throw new IllegalArgumentException("Navigation destination " + dest
+ " referenced from action "
+ NavDestination.getDisplayName(mContext, resId)
+ " cannot be found from the current destination " + currentNode);
} else {
throw new IllegalArgumentException("Navigation action/destination " + dest
+ " cannot be found from the current destination " + currentNode);
}
}
// 这行代码就是我们要关心的主线流程
navigate(node, combinedArgs, navOptions, navigatorExtras);
}
3 NavController.navigation
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = false;
boolean launchSingleTop = false;
if (navOptions != null) {
if (navOptions.getPopUpTo() != -1) {
popped = popBackStackInternal(navOptions.getPopUpTo(),
navOptions.isPopUpToInclusive());
}
}
// 同学们,这里又回到了,最初开始的代码哦
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
// 同学们 还记得 新的目的地么? OK 分析完毕了,无效重复分析了
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
if (newDest != null) {
if (!(newDest instanceof FloatingWindow)) {
// We've successfully navigating to the new destination, which means
// we should pop any FloatingWindow destination off the back stack
// before updating the back stack with our new destination
//noinspection StatementWithEmptyBody
while (!mBackStack.isEmpty()
&& mBackStack.peekLast().getDestination() instanceof FloatingWindow
&& popBackStackInternal(
mBackStack.peekLast().getDestination().getId(), true)) {
// Keep popping
}
}
// When you navigate() to a NavGraph, we need to ensure that a new instance
// is always created vs reusing an existing copy of that destination
ArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>();
NavDestination destination = newDest;
if (node instanceof NavGraph) {
do {
NavGraph parent = destination.getParent();
if (parent != null) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, parent,
finalArgs, mLifecycleOwner, mViewModel);
hierarchy.addFirst(entry);
// Pop any orphaned copy of that navigation graph off the back stack
if (!mBackStack.isEmpty()
&& mBackStack.getLast().getDestination() == parent) {
popBackStackInternal(parent.getId(), true);
}
}
destination = parent;
} while (destination != null && destination != node);
}
// Now collect the set of all intermediate NavGraphs that need to be put onto
// the back stack
destination = hierarchy.isEmpty()
? newDest
: hierarchy.getFirst().getDestination();
while (destination != null && findDestination(destination.getId()) == null) {
NavGraph parent = destination.getParent();
if (parent != null) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs,
mLifecycleOwner, mViewModel);
hierarchy.addFirst(entry);
}
destination = parent;
}
NavDestination overlappingDestination = hierarchy.isEmpty()
? newDest
: hierarchy.getLast().getDestination();
// Pop any orphaned navigation graphs that don't connect to the new destinations
//noinspection StatementWithEmptyBody
while (!mBackStack.isEmpty()
&& mBackStack.getLast().getDestination() instanceof NavGraph
&& ((NavGraph) mBackStack.getLast().getDestination()).findNode(
overlappingDestination.getId(), false) == null
&& popBackStackInternal(mBackStack.getLast().getDestination().getId(), true)) {
// Keep popping
}
mBackStack.addAll(hierarchy);
// The mGraph should always be on the back stack after you navigate()
if (mBackStack.isEmpty() || mBackStack.getFirst().getDestination() != mGraph) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs,
mLifecycleOwner, mViewModel);
mBackStack.addFirst(entry); // 留意一下,每次都会管理 进栈
}
// And finally, add the new destination with its default args
NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
mBackStack.add(newBackStackEntry);
} else if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
launchSingleTop = true;
NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast();
if (singleTopBackStackEntry != null) {
singleTopBackStackEntry.replaceArguments(finalArgs);
}
}
updateOnBackPressedCallbackEnabled();
if (popped || newDest != null || launchSingleTop) {
dispatchOnDestinationChanged();
}
}