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:
创建导航图的具体步骤:
- 创建导航图资源文件,在 New Source File 对话框中,ResourceType 选择 Navigation,Directory Name 填 navigation,这样就会在模块的 src/main/res 下新建一个 navigation 目录以及导航图资源文件
- 修改资源文件内容,指定各目的地以及连接它们的操作
举个例子,在导航图中放入三个 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 中。
参考资料: