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

    }

相关推荐
五味香17 小时前
Java学习,List截取
android·java·开发语言·python·学习·golang·kotlin
xvch1 天前
Kotlin 2.1.0 入门教程(三)
android·kotlin
小李飞飞砖1 天前
kotlin的协程的基础概念
开发语言·前端·kotlin
深色風信子2 天前
Kotlin Bytedeco OpenCV 图像图像49 仿射变换 图像裁剪
opencv·kotlin·javacpp·bytedeco·仿射变换 图像裁剪
五味香2 天前
Java学习,List移动元素
android·java·开发语言·python·学习·kotlin·list
studyForMokey2 天前
【Android学习】Kotlin随笔
android·学习·kotlin
zhangphil3 天前
Android BitmapShader实现狙击瞄具十字交叉线准星,Kotlin
android·kotlin
柯南二号4 天前
【Kotlin】上手学习之控制流程篇
android·开发语言·学习·kotlin
划水哥~4 天前
Kotlin数据类
开发语言·kotlin
深色風信子4 天前
Kotlin Bytedeco OpenCV 图像图像55 图像透视变换
opencv·kotlin·透视变换·bytedeco