Navigation 基本原理

Navigation 组件旨在用于具有一个主 Activity 和多个 Fragment 目的地的应用。主 Activity 与导航图相关联,且包含一个负责根据需要交换目的地的 NavHostFragment。在具有多个 Activity 目的地的应用中,每个 Activity 均拥有其自己的导航图。

1、基础使用

添加依赖:

dependencies {
    def nav_version = "2.3.2"

    // Java language implementation
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-ui:$nav_version"
    
    // 示例还需要 BottomNavigationView,在 Material 包中
    implementation 'com.google.android.material:material:1.1.0'
}

1.1 创建导航图

导航图是一种资源文件,主要包含目的地和操作两个角色:

  • 目的地:指应用中的不同内容区域,如 Activity、Fragment
  • 操作:目的地之间的逻辑连接,表示用户可以采取的路径,用于连接各个目的地

如下是一个由 5 个操作连接的 6 个目的地的预览图,图片来自于 Google Developer

创建导航图的具体步骤:

  1. 创建导航图资源文件,在 New Source File 对话框中,ResourceType 选择 Navigation,Directory Name 填 navigation,这样就会在模块的 src/main/res 下新建一个 navigation 目录以及导航图资源文件
  2. 修改资源文件内容,指定各目的地以及连接它们的操作

举个例子,在导航图中放入三个 Fragment:

<?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"
    app:startDestination="@id/page1Fragment">

    <fragment
        android:id="@+id/page1Fragment"
        android:name="com.demo.navigation.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
        android:id="@+id/page2Fragment"
        android:name="com.demo.navigation.MainPage2Fragment"
        android:label="fragment_page2"
        tools:layout="@layout/fragment_main_page2">
        <action
            android:id="@+id/action_page1"
            app:destination="@id/page1Fragment" />
        <action
            android:id="@+id/action_page3"
            app:destination="@id/page3Fragment" />
    </fragment>

    <fragment
        android:id="@+id/page3Fragment"
        android:name="com.demo.navigation.MainPage3Fragment"
        android:label="fragment_page3"
        tools:layout="@layout/fragment_main_page3">
        <action
            android:id="@+id/action_page2"
            app:destination="@id/page2Fragment" />
    </fragment>
</navigation>

解释一下上述代码中的部分属性:

  • android:name:实现该目的地的全类名
  • android:label:该目的地的用户可读名称,如果使用 setupWithNavController() 将 NavGraph 连接到 Toolbar,就可能在界面上看到此字段
  • app:startDestination:指定起始目的地。起始目的地是用户应用时看到的第一个屏幕,也是用户退出应用时看到的最后一个屏幕
  • action:至少包含操作自身的 ID 和下一个目的地的 ID

★ 注意:导航图尽量设计成树形结构,不要出现回路。

1.2 向 Activity 添加 NavHost

导航宿主 Navigation Host 是 Navigation 组件的核心部分之一。导航宿主是一个空容器,必须派生于 NavHost,用户在应用中导航时,目的地会在该容器中交换进出。其中,Navigation 组件的默认 NavHost 实现 NavHostFragment 负责处理 Fragment 目的地的交换:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:itemTextColor="#FF0000"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

需要注意的属性:

  • android:name:NavHost 实现类的名称
  • app:navGraph:将 NavHostFragment 与导航图相关联
  • app:defaultNavHost=“true”:确保 NavHostFragment 会拦截系统返回按钮。只能有一个默认的 NavHost,即便同一个布局中有多个宿主 ,也只能指定一个默认的 NavHost

1.3 转到目的地

我们使用 ID 导航的方式跳转到指定目的地:

public class MainPage1Fragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main_page1, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Button btn = view.findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
            	// 跳转到 ID 为 action_page2 的目的地
                Navigation.findNavController(view).navigate(R.id.action_page2);
            }
        });
    }
}

此外,Navigation 给我们维护了一个回退栈,所以你可以通过返回键逐层回退到上一个目的地,它等同于使用了:

	Navigation.findNavController(view).navigateUp();

1.4 结合 BottomNavigationView

Navigation 还可以与 BottomNavigationView 配合使用,之前我们已经添加了 BottomNavigationView 的依赖并在 Activity 的布局中加入了该组件,其所使用的 menu 中需要指定 item id 为导航图中对应 Fragment 的 id:

<?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="1" />
    <item
        android:id="@+id/page2Fragment"
        android:icon="@drawable/ic_launcher_foreground"
        android:title="2" />
    <item
        android:id="@+id/page3Fragment"
        android:icon="@drawable/ic_launcher_foreground"
        android:title="3" />
</menu>

将 BottomNavigationView 与 NavController 进行绑定:

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BottomNavigationView bottomNavigationView = findViewById(R.id.nav_bottom);
        NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
        NavController navController = navHostFragment.getNavController();
        NavigationUI.setupWithNavController(bottomNavigationView, navController);
    }

需要注意通过点击 BottomNavigationView 实现的目的地跳转不会被计入 Navigation 维护的回退栈。

2、基本原理

Navigation 相关的类图如下:

主要关系如下:

  • Activity 需要绑定一个 NavHostFragment,后者持有的 NavHostController 用来控制导航行为
  • NavController 持有 NavigatorProvider 以获取 Navigator,调用 NavController.navigate() 会调用 Navigator.navigate(),获取从回退栈中获取到的 NavDestination 中封装的目的地信息,然后导航至目标

源码主要从两个角度入手,初始化过程与使用过程。

2.1 初始化过程

初始化的起点是 NavHostFragment,我们主要关注 NavHostFragment 的创建与生命周期相关方法:

  • create():创建一个 NavHostFragment 对象,并把导航图资源 ID 和起始目的地参数封装到 Bundle 中作为参数设置给 NavHostFragment
  • onInflate():解析布局文件中 FragmentContainerView 的部分属性,获取导航图资源 ID 以及当前 Activity 是否为默认导航宿主
  • onCreate():创建 NavController 并设置 LifecycleOwner、OnBackPressedDispatcher、ViewModelStore 以及导航图对象,并导航至初始导航图
  • onCreateView():创建 FragmentContainerView
  • onViewCreated():将 NavController 与给定的 View 关联

2.1.1 create() 与 onInflate()

先看 NavHostFragment 的创建过程:

	@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;
        // 导航图资源 ID
        if (graphResId != 0) {
            b = new Bundle();
            b.putInt(KEY_GRAPH_ID, graphResId);
        }
        // 起始目的地参数
        if (startDestinationArgs != null) {
            if (b == null) {
                b = new Bundle();
            }
            b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
        }

		// 创建一个新的 NavFragment 对象,将 b 作为参数封装进去
        final NavHostFragment result = new NavHostFragment();
        if (b != null) {
            result.setArguments(b);
        }
        return result;
    }

如果在调用 create() 时传入了导航图资源 ID 或初始目的地参数,就保存在 Fragment 的 mArguments 中。

接下来会在 onInflate() 中对 Activity 布局中的 FragmentContainerView 进行解析,主要是获取导航图的资源 ID 以及该容器是否是默认的导航宿主:

	@CallSuper
    @Override
    public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
            @Nullable Bundle savedInstanceState) {
        super.onInflate(context, attrs, savedInstanceState);

		// 解析 app:navGraph="@navigation/nav_graph" 并保存导航图资源 ID
        final TypedArray navHost = context.obtainStyledAttributes(attrs,
                androidx.navigation.R.styleable.NavHost);
        final int graphId = navHost.getResourceId(
                androidx.navigation.R.styleable.NavHost_navGraph, 0);
        if (graphId != 0) {
            mGraphId = graphId;
        }
        navHost.recycle();

		// 解析 app:defaultNavHost="true" 并保存当前的 NavHostFragment 是否为默认的导航宿主
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
        final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
        if (defaultHost) {
            mDefaultNavHost = true;
        }
        a.recycle();
    }

2.2.2 onCreate()

接下来看 onCreate(),主要工作是创建 NavController 并对其进行相关设置:

	@CallSuper
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        final Context context = requireContext();

		// 创建 NavController 对象并初始化
        mNavController = new NavHostController(context);
        mNavController.setLifecycleOwner(this);
        mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
        // Set the default state - this will be updated whenever
        // onPrimaryNavigationFragmentChanged() is called
        mNavController.enableOnBackPressed(
                mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
        mIsPrimaryBeforeOnCreate = null;
        mNavController.setViewModelStore(getViewModelStore());
        // 向 mNavController 中的 NavigatorProvider 中添加必要的 Navigator
        onCreateNavController(mNavController);

		// 状态恢复操作,需要重新获取 mDefaultNavHost、mGraphId 等信息
        Bundle navState = null;
        if (savedInstanceState != null) {
            navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
            if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
                mDefaultNavHost = true;
                getParentFragmentManager().beginTransaction()
                        .setPrimaryNavigationFragment(this)
                        .commit();
            }
            mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
        }

        if (navState != null) {
            // Navigation controller state overrides arguments
            mNavController.restoreState(navState);
        }
        // 导航图资源 ID 可以在布局文件中设置,也可以在创建 NavHostFragment 时通过
        // create() 传递进来,可以通过这两种方式给 mNavController 设置导航图资源 ID
        if (mGraphId != 0) {
            // Set from onInflate()
            mNavController.setGraph(mGraphId);
        } else {
            // See if it was set by NavHostFragment.create()
            final Bundle args = getArguments();
            final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
            final Bundle startDestinationArgs = args != null
                    ? args.getBundle(KEY_START_DESTINATION_ARGS)
                    : null;
            if (graphId != 0) {
                mNavController.setGraph(graphId, startDestinationArgs);
            }
        }

        // We purposefully run this last as this will trigger the onCreate() of
        // child fragments, which may be relying on having the NavController already
        // created and having its state restored by that point.
        super.onCreate(savedInstanceState);
    }

在 onCreateNavController() 中向 NavController 的 NavigatorProvider 中添加 DialogFragmentNavigator 和 FragmentNavigator 两种导航器:

	/**
     * 当创建 getNavController() 中返回的那个 NavController 对象时回调。如果支持自定义的
     * 目的地类型,它们的 Navigator 必须在这里添加,以确保在导航图被填充/设置之前可用。
     * 默认情况下,添加一个 FragmentNavigator
     * 只在 onCreate() 中调用一次,并且不应在子类中直接调用
     */
	@CallSuper
    protected void onCreateNavController(@NonNull NavController navController) {
    	// 创建一个 DialogFragmentNavigator 并添加到 NavigatorProvider 中
        navController.getNavigatorProvider().addNavigator(
                new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
		// 创建一个 FragmentNavigator 并添加到 NavigatorProvider 中
        navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
    }

	@Deprecated
    @NonNull
    protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
    	// getContainerId() 是获取 Fragment 的父容器的 ID,默认为 R.id.nav_host_fragment_container
        return new FragmentNavigator(requireContext(), getChildFragmentManager(),
                getContainerId());
    }

这些被添加的导航器对象都会被保存在 NavigatorProvider 的 mNavigators 中:

	private final HashMap<String, Navigator<? extends NavDestination>> mNavigators =
            new HashMap<>();
            
	@Nullable
    public final Navigator<? extends NavDestination> addNavigator(
            @NonNull Navigator<? extends NavDestination> navigator) {
        String name = getNameForNavigator(navigator.getClass());

        return addNavigator(name, navigator);
    }
    
    @CallSuper
    @Nullable
    public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
            @NonNull Navigator<? extends NavDestination> navigator) {
        ...
        return mNavigators.put(name, navigator);
    }

回到 onCreate(),还有一个关键操作就是把导航图对象设置给 mNavController:

	@CallSuper
    public void setGraph(@NavigationRes int graphResId) {
        setGraph(graphResId, null);
    }

	@CallSuper
    public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
    	// NavInflater 将 graphResId 解析成 NavGraph 对象
        setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
    }
    
	/**
	* 将导航图 mGraph 设置为指定的图,当前导航图的数据(包括回退栈)都会被替换掉
	*/
	@CallSuper
    public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
    	// 首次调用该方法时由于 mGraph 还是空,不会弹回退栈
        if (mGraph != null) {
            // Pop everything from the old graph off the back stack
            popBackStackInternal(mGraph.getId(), true);
        }
        mGraph = graph;
        onGraphCreated(startDestinationArgs);
    }

onGraphCreated() 会负责导航图对象刚创建时的初始化操作,并且会导航至初始目的地:

	private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
		// 恢复 mNavigatorStateToRestore 中保存的 Navigator 的状态
        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());
                    ...
                }
                Bundle args = state.getArgs();
                if (args != null) {
                    args.setClassLoader(mContext.getClassLoader());
                }
                NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
                        mLifecycleOwner, mViewModel,
                        state.getUUID(), state.getSavedState());
                mBackStack.add(entry);
            }
            updateOnBackPressedCallbackEnabled();
            mBackStackToRestore = null;
        }
        // 导航图不为空并且回退栈为空,在没有深度连接的情况下就导航至初始目的地
        if (mGraph != null && mBackStack.isEmpty()) {
            boolean deepLinked = !mDeepLinkHandled && mActivity != null
                    && handleDeepLink(mActivity.getIntent());
            if (!deepLinked) {
                // Navigate to the first destination in the graph
                // if we haven't deep linked to a destination
                // 如果导航图中没有深度连接,就先导航到初始目的地
                navigate(mGraph, startDestinationArgs, null, null);
            }
        } else {
            dispatchOnDestinationChanged();
        }
    }

除了恢复各种状态之外,主要就是通过 navigate() 导航到初始目的地。导航过程实际上就是将目的地对应的 Fragment 对象通过反射的方式创建出来,并替换到容器 FragmentContainerView 之中。

navigate() 实际上是处理所有导航跳转需要用到的方法,后续介绍导航过程时再详细介绍,在初始化阶段我们只需了解 onCreate() 借助 onGraphCreated() 调用 navigate() 导航到了初始目的地即可。

2.2.3 onCreateView() 与 onViewCreated()

	@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
		// 创建 FragmentContainerView
        FragmentContainerView containerView = new FragmentContainerView(inflater.getContext());
        // 当通过 XML 添加时,这没有任何效果(因为此FragmentContainerView会自动获得ID),
        // 但这确保了在以编程方式添加 NavHostFragment 的情况下,该视图作为该 Fragment 的
        // 视图层次结构的一部分存在,这对于子 Fragment 事务是必需的
        containerView.setId(getContainerId());
        return containerView;
    }
    
	@Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ...
        // 将 NavController 与给定的 View 关联,这样就可以通过 findNavController()
        // 找到与这个 View 关联的 NavController
        Navigation.setViewNavController(view, mNavController);
        // When added programmatically, we need to set the NavController on the parent - i.e.,
        // the View that has the ID matching this NavHostFragment.
        if (view.getParent() != null) {
            mViewParent = (View) view.getParent();
            if (mViewParent.getId() == getId()) {
                Navigation.setViewNavController(mViewParent, mNavController);
            }
        }
    }

到这里初始化工作基本完成,实际上就是将 NavController 相关联的对象都初始化了一遍。

2.2 导航过程

通常我们使用如下方式进行导航:

	Navigation.findNavController(view).navigate(R.id.action_page2);

2.2.1 findNavController()

首先要通过 Navigation 找到 NavController,实际上在 NavHostFragment 的 onViewCreated() 中,调用 Navigation.setViewNavController(view, mNavController) 执行的其实是 view.setTag(R.id.nav_controller_view_tag, controller),现在 findNavController() 实际上就是从 view 上取出这个 controller:

	@NonNull
    public static NavController findNavController(@NonNull View view) {
        NavController navController = findViewNavController(view);
        if (navController == null) {
            throw new IllegalStateException("View " + view + " does not have a NavController set");
        }
        return navController;
    }
	
	@Nullable
    private static NavController findViewNavController(@NonNull View view) {
        while (view != null) {
            NavController controller = getViewNavController(view);
            if (controller != null) {
                return controller;
            }
            ViewParent parent = view.getParent();
            view = parent instanceof View ? (View) parent : null;
        }
        return null;
    }

	@Nullable
    private static NavController getViewNavController(@NonNull View view) {
    	// 从 view 的 Tag 上取出 NavController
        Object tag = view.getTag(R.id.nav_controller_view_tag);
        NavController controller = null;
        if (tag instanceof WeakReference) {
            controller = ((WeakReference<NavController>) tag).get();
        } else if (tag instanceof NavController) {
            controller = (NavController) tag;
        }
        return controller;
    }

2.2.2 navigate()

然后调用 NavController 的 navigate():

    public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
            @Nullable Navigator.Extras navigatorExtras) {
        // 获取回退栈栈顶元素的目的地,也就是当前正在显示中的目的地
        NavDestination currentNode = mBackStack.isEmpty()
                ? mGraph
                : mBackStack.getLast().getDestination();
        ...
        @IdRes int destId = resId;
        // 从当前目的地中,根据 Action 的资源 ID 找到对应的 Action
        final NavAction navAction = currentNode.getAction(resId);
        Bundle combinedArgs = null;
        // 参数处理
        if (navAction != null) {
            if (navOptions == null) {
                navOptions = navAction.getNavOptions();
            }
            destId = navAction.getDestinationId();
            Bundle navActionArgs = navAction.getDefaultArguments();
            if (navActionArgs != null) {
                combinedArgs = new Bundle();
                combinedArgs.putAll(navActionArgs);
            }
        }

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

        ...

		// 根据 destId 找到下一个目的地节点
        NavDestination node = findDestination(destId);
        if (node == null) {
            final String dest = NavDestination.getDisplayName(mContext, destId);
            ...
        }
        // 向下一个目的地进行导航
        navigate(node, combinedArgs, navOptions, navigatorExtras);
    }

这一步的 navigate() 是根据传入的 resId 找到了对应的目的地对象 NavDestination,并且将参数做了整合,最后又调用了一个 navigate() 执行导航:

	private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        boolean popped = false;
        boolean launchSingleTop = false;
        if (navOptions != null) {
            if (navOptions.getPopUpTo() != -1) {
                popped = popBackStackInternal(navOptions.getPopUpTo(),
                        navOptions.isPopUpToInclusive());
            }
        }
        // 从 mNavigatorProvider 中根据 NavDestination 的名字获取到 Navigator 对象
        Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                node.getNavigatorName());
        Bundle finalArgs = node.addInDefaultArgs(args);
        // navigator 根据目的地和参数进行导航,得到一个新的目的地节点
        NavDestination newDest = navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);
        ...
    }

Navigator 是一个抽象类,其具体子类有 ActivityNavigator、FragmentNavigator、DialogFragmentNavigator 等等,这里选择 FragmentNavigator 来看导航过程:

	@Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mFragmentManager.isStateSaved()) {
            return null;
        }
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        // 反射方式创建 Fragment 实例
        // 参数中的 mFragmentManager 是 NavHostFragment 的子 FragmentManager 
        final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
        frag.setArguments(args);
        // 获得 Fragment 事务
        final FragmentTransaction ft = mFragmentManager.beginTransaction();

		// 动画处理
        int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
        int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
        int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
        int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }

		// 用 frag 替换容器 mContainerId 当前显示的 Fragment
        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
        // mBackStack 回退栈为空,说明当前导航的是初始目的地
        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;
        }
    }

主要就是以反射方式创建出 destination 对应的 Fragment 对象,并添加到 Fragment 容器 —— 即在 Activity 布局文件中声明的 FragmentContainerView 中,此外还会将该 Fragment 添加到回退栈 mBackStack 中。

参考资料:

Navigation 组件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值