参考:
Navigation
Navigation的基本使用
目录
- 一、Navigation是什么?
- 二、Navigation的三大件
- 三、基本使用
-
- [1. 导入依赖](#1. 导入依赖)
- [2. 创建三个不同的fragment用于页面的切换](#2. 创建三个不同的fragment用于页面的切换)
- [3. 创建导航图](#3. 创建导航图)
- [4. 设置导航menu](#4. 设置导航menu)
- [5. Activity使用](#5. Activity使用)
- 四、Navigation源码原理解析
一、Navigation是什么?
Navigation 是一个框架,用于在 Android 应用中的"目标"之间导航,该框架提供一致的 API,无论目标是作为 Fragment、Activity 还是其他组件实现。
Navigation是管理Fragment之间导航的组件库,特别在实现单个Activity多个Fragment的管理模式更加灵活。
利用Navigation的三大组件,我们可以自由控制管理fragment的切换和数据传递和回退栈,不要再想以前一样通过FragmentManager进行replace或者show了,以及事务的提交,在数据传递方面,也不会通过fragmentID和接口回调的方式进行传递,大大方便了我们的代码编写。
二、Navigation的三大件
- 导航图:在一个集中位置包含所有导航相关信息的 XML 资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。
- NavHost:显示导航图中目标的空白容器。导航组件包含一个默认 NavHost 实现 (NavHostFragment),可显示Fragment 目标。
- NavController:在 NavHost 中管理应用导航的对象。当用户在整个应用中移动时,NavController 会安排 NavHost 中目标内容的交换。
在应用中导航时,您告诉 NavController,您想沿导航图中的特定路径导航至特定目标,或直接导航至特定目标。NavController 便会在 NavHost 中显示相应目标。
导航组件提供各种其他优势,包括以下内容:
- 处理 Fragment 事务
- 默认情况下,正确处理往返操作。
- 为动画和转换提供标准化资源。
- 实现和处理深层链接。
- 包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。
- Safe Args - 可在目标之间导航和传递数据时提供类型安全的 Gradle 插件。
- ViewModel 支持 - 您可以将 ViewModel 的范围限定为导航图,以在图表的目标之间共享与界面相关的数据。
三、基本使用
项目框架结构图
项目结构图
1. 导入依赖
在app目录下build.gradle导入Navigation依赖
//navigation的依赖库
def nav_version = "2.3.2"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation 'com.google.android.material:material:1.1.0'
2. 创建三个不同的fragment用于页面的切换
fragment_main_page1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
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:textAllCaps="false"
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>
MainPage1Fragment.kt
package com.example.mynavigation
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
// 第一个Fragment(默认启动)
class MainPage1Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//return super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_main_page1, 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);
}
}
}
fragment_main_page2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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: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>
MainPage2Fragment.kt
package com.example.mynavigation
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
// 第二个Fragment
class MainPage2Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//return super.onCreateView(inflater, container, savedInstanceState)
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)
}
}
}
fragment_main_page3.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:layout_gravity="center"
android:textSize="20sp"
android:textColor="#3F51B5"
android:textStyle="bold"
/>
<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>
MainPage3Fragment.kt
package com.example.mynavigation
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
// 第三个Fragment
class MainPage3Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//return super.onCreateView(inflater, container, savedInstanceState)
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() // 回退上一步
}
}
}
3. 创建导航图
- 在"Project"窗口中,右键点击 res 目录,然后依次选择 New > Android Resource File。此时系统会显示New Resource File 对话框。
- 在 File name 字段中输入名称,例如"nav_graph_main"。
- 从 Resource type 下拉列表中选择 Navigation,然后点击 OK。
当您添加首个导航图时,Android Studio 会在 res 目录内创建一个 navigation 资源目录。该目录包含您的导航图资源文件(例如 nav_graph_main.xml)。
nav_graph_main.xml
<?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.xml"
app:startDestination="@id/page1Fragment">
<!-- app:startDestination 表示默认启动的Fragment,这里默认启动是mainPage1Fragment-->
<!-- 这是第一个Fragment -->
<fragment
android:id="@+id/page1Fragment"
android:name="com.example.mynavigation.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>
<!-- 这是第二个Fragment -->
<fragment
android:id="@+id/page2Fragment"
android:name="com.example.mynavigation.MainPage2Fragment"
android:label="fragment_page2"
tools:layout="@layout/fragment_main_page2">
<action
android:id="@+id/action_page3"
app:destination="@id/page3Fragment" />
<action
android:id="@+id/action_page1"
app:destination="@id/page1Fragment" />
</fragment>
<!-- 这是第三个Fragment -->
<fragment
android:id="@+id/page3Fragment"
android:name="com.example.mynavigation.MainPage3Fragment"
android:label="fragment_page3"
tools:layout="@layout/fragment_main_page3">
<action
android:id="@+id/action_page2"
app:destination="@id/page2Fragment" />
</fragment>
</navigation>
app:startDestination 表示设置默认启动,有两种方式可以设置默认启动的fragment
方式一:直接在xml布局为中的app:startDestination设置默认的fragment
方式二:
点击图标1进入该Design界面
点击图标2可以添加相关的fragment
点击图标3中的fragmeng,右击选择"Set as Start Destination",即可设置为默认启动fragment
4. 设置导航menu
- 在"Project"窗口中,右键点击 res 目录,然后依次选择 New > Android Resource File。此时系统会显示New Resource File 对话框。
- 在 File name 字段中输入名称,例如"menu"。
- 从 Resource type 下拉列表中选择 Menu,然后点击 OK。
menu.xml
<?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>
5. Activity使用
布局文件
activity_main.xml
<?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"
tools:context=".MainActivity">
<!--
app:defaultNavHost = "true" 拦截系统back键
app:navGraph="@navigation/nav_graph_main" 指定主导航 才能显示默认的fragment
NavHostFragment 主导航 my_nav_host_fragment此id指向主导航
-->
<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>
MainActivity.kt
package com.example.mynavigation
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.navigation.Navigation
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
var bottomNavigationView : BottomNavigationView ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bottomNavigationView = findViewById(R.id.nav_view)
// 让navigation工作 绑定
// NavHostFragment 主导航
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?
val controller = navHostFragment?.navController
// 底部导航Tab + Navigation
if (controller != null) {
NavigationUI.setupWithNavController(bottomNavigationView!!, controller)
}
}
}
四、Navigation源码原理解析
原理图
-
NavHostFragment.create
@NonNull
public static NavHostFragment create(@NavigationRes int graphResId) {
return create(graphResId, null);
}@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); // 把graphID存入到Bundle
}
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.onInflate
@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; // 就把此属性解析出来,activity_main.xml 的 app:navGraph="@navigation/nav_graph_main" } 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; // 就把此属性解析出来,activity_main.xml 的 app:defaultNavHost="true" } a.recycle();
}
-
NavHostFragment.onCreateNavController
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
// 这里是通过导航暴露者,获取到 NavigationProvider 对象
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}public class NavigatorProvider {
// 把导航页面,例如 三个Fragment保存 与 key保存 记录
private static final HashMap<Class<?>, String> sAnnotationNames = new HashMap<>();
}protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(),
getContainerId()); // 此ID,如果不写xml文件,单纯用代码实现的时候,需要得到一个父容器ID
} -
NavHostFragment.onCreateNav
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
final Context context = requireContext();mNavController = new NavHostController(context); // 此处 实例化出 NavHostController 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) { // 如果GraphID不等于空 // Set from onInflate() mNavController.setGraph(mGraphId); // 设置GraphID,此处意义重大,会获取nav_graph_main里面的action等导航信息 } 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);
}
-
NavController.setGraph
@CallSuper
public void setGraph(@NavigationRes int graphResId) {
setGraph(graphResId, null);
}@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
// getNavInflater().inflate(graphResId) 开始真正的解析 nav_graph_main里面的action等导航信息,还要确定startDestination的Fragment
// startDestinationArgs 代表要启动 app:startDestination="@id/page1Fragment" 的参数信息
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}@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(); }
}
// 退会后,看到 setGraph 细节
@CallSuper // 可以看到 把 GraphID xml 里面的内容 转变成 NavGraph对象了
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); // 此函数就能看到,如何把我们的默认Fragment UI给显示出来了
} -
NavController.onGraphCreated
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
// 这个 这种 if的 ,对我们的主线流程意义不大,先跳过
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;
}// 此代码就比较关键了,如果 前面辛辛苦苦保存的mGraph对象不为空,并且,栈里面是空的 取不出来的情况下, 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(); }
}
-
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);
// 构建新的目的地对象,此对象,就是后续要完成的目标了
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.navigate
@Nullable
public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);
-
FragmentNavigator.navigate
@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;
}// 根据Destination目的地,获取到第一个Fragmet 也就是 MainPage1Fragment实例 // 下面代码是反射 去 实例化 我们的 第一个Fragment String className = destination.getClassName(); if (className.charAt(0) == '.') { className = mContext.getPackageName() + className; } // instanceFragment反射的细节,可以看看 final Fragment frag = instantiateFragment(mContext, mFragmentManager, className, args); frag.setArguments(args); final FragmentTransaction ft = mFragmentManager.beginTransaction(); // Fragment的进出 动画 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); } // 然后把mContainerId,替换到ft,相当于把 我们的 第一个Fragment加入到了 UI上面了 // 就是把 MainPage1Fragment 加入到 activity_main.xml 的 FragmentContainerView // mContainerId(就是 FragmentContainerView)相当于容器 // frag(就是 我们的第一个Fragment实例 MainPage1Fragment) 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; }
}
@Deprecated
@NonNull
public Fragment instantiateFragment(@NonNull Context context,
@NonNull FragmentManager fragmentManager,
@NonNull String className, @SuppressWarnings("unused") @Nullable Bundle args) {
// instantiate 反射的细节看看
return fragmentManager.getFragmentFactory().instantiate(
context.getClassLoader(), className);
}@NonNull
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
try {
Class<? extends Fragment> cls = loadFragmentClass(classLoader, className);
return cls.getConstructor().newInstance(); // 把最终方式的Fragment结果 给返回
} catch (java.lang.InstantiationException e) {
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (IllegalAccessException e) {
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (NoSuchMethodException e) {
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ ": could not find Fragment constructor", e);
} catch (InvocationTargetException e) {
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ ": calling Fragment constructor caused an exception", e);
}
} -
小结
第一步:解析所有的目的地
第二步:放到一个HashMap里面全部保存好
第三步:获取第一个目的地Fragment
第四步:初始化目的地的所有信息
第五步:显示第一个目的地Fragment 画面
11 NavHostFragment.onViewCreated
这里有一个 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角度上分析源码
-
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) // 在这里开始导航 }
}
-
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);
}
-
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(); }
}