Navigation的基本使用

参考:
Navigation
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. 创建导航图

  1. 在"Project"窗口中,右键点击 res 目录,然后依次选择 New > Android Resource File。此时系统会显示New Resource File 对话框。
  2. 在 File name 字段中输入名称,例如"nav_graph_main"。
  3. 从 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

  1. 在"Project"窗口中,右键点击 res 目录,然后依次选择 New > Android Resource File。此时系统会显示New Resource File 对话框。
  2. 在 File name 字段中输入名称,例如"menu"。
  3. 从 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源码原理解析

原理图

  1. 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;
    

    }

  2. 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();
    

    }

  3. 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
    }

  4. 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);
    

    }

  5. 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给显示出来了
    }

  6. 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();
     }
    

    }

  7. 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();
     }
    

    }

  8. Navigator.navigate

    @Nullable
    public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args,
    @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);

  1. 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);
    }
    }

  2. 小结
    第一步:解析所有的目的地
    第二步:放到一个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角度上分析源码

  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();
     }
    

    }

相关推荐
菠菠萝宝1 小时前
【YOLOv8】安卓端部署-1-项目介绍
android·java·c++·yolo·目标检测·目标跟踪·kotlin
恋猫de小郭2 小时前
Kotlin Multiplatform 未来将采用基于 JetBrains Fleet 定制的独立 IDE
开发语言·ide·kotlin
枫__________12 小时前
kotlin 协程 job的cancel与cancelAndJoin区别
android·开发语言·kotlin
鸠摩智首席音效师1 天前
如何在 Ubuntu 上配置 Kotlin 应用环境 ?
linux·ubuntu·kotlin
jikuaidi6yuan3 天前
Java与Kotlin在鸿蒙中的地位
java·kotlin·harmonyos
liulanba3 天前
Kotlin的data class
前端·微信·kotlin
小白学大数据3 天前
使用OkHttp进行HTTPS请求的Kotlin实现
爬虫·python·okhttp·https·kotlin
CodeCraft Studio3 天前
【实用教程】如何利用 JxBrowser 在 Kotlin 中实现屏幕共享
android·开发语言·kotlin
zhangphil3 天前
Kotlin return与return@forEachIndexed
kotlin
yujunlong39194 天前
flutter pigeon gomobile 插件中使用go工具类
flutter·golang·kotlin·swift