NavHost:显示 Navigation graph 中目标的空白容器。Navigation 组件包含一个默认 NavHost 实现 (NavHostFragment),可显示 Fragment 目标。NavHostFragment在布局中提供了一个区域,用于进行包含导航。
接下来我们看一下它的源码:
public class NavHostFragment extends Fragment implements NavHost {
@CallSuper
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (mDefaultNavHost) {
requireFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
}
}
复制代码可以看到它就是一个Fragment,在onAttach生命周期开启事务将其自己设置为PrimaryFragment了
,当然通过defaultNavHost条件判断的,这个布尔值看着眼熟吗?没错,就是我们在xml布局中设置的那一个。
<fragment
android:id="@+id/fragment_home"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_main"/>
接着看它的onCreate生命周期
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
final Context context = requireContext();
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());
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) {
// 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);
}
@SuppressWarnings({"WeakerAccess", "deprecation"})
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
@NonNull
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(),
getContainerId());
}
我们看到在onCreate生命周期中创建了一个NavController
,并且为这个NavController创建了一个 Navigator extends FragmentNavigator.Destination>
添加了进去,我们跟踪onCreateNavController(mNavController)、createFragmentNavigator
,发现它创建了一个FragmentNavigator
,这个类是做什么的呢?
FragmentNavigator继承了Navigator
,查看注释我们知道它是为每个导航设置策略的,然后片段之间通过导航切换都是由它来操作的
,下面会详细介绍的,这里先简单看下。
接下来我们看到为NavController设置了setGraph(),也就是我们XML里面定义的navGraph,布局导航里面的Fragment及action跳转等信息。
还有就是onCreateView,onViewCreated等生命周期方法,基本就是加载布局设置ID的方法了。
下面我们跟到NavController.setGraph()
中看下是怎样将我们设计的fragment添加进去的?
/**
* Sets the {@link NavGraph navigation graph} to the specified resource.
* Any current navigation graph data (including back stack) will be replaced.
*
* The inflated graph can be retrieved via {@link #getGraph()}.
*
* @param graphResId resource id of the navigation graph to inflate
* @param startDestinationArgs arguments to send to the start destination of the graph
*
* @see #getNavInflater()
* @see #setGraph(NavGraph, Bundle)
* @see #getGraph
*/
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
/**
* Sets the {@link NavGraph navigation graph} to the specified graph.
* Any current navigation graph data (including back stack) will be replaced.
*
* The graph can be retrieved later via {@link #getGraph()}.
*
* @param graph graph to set
* @see #setGraph(int, Bundle)
* @see #getGraph
*/
@CallSuper
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);
}
我们看如果设置的graph不为null,它执行了popBackStackInternal,看注释的意思是从之前的就的graph栈弹出所有的graph
:
/**
* Attempts to pop the controller's back stack back to a specific destination. This does
* not handle calling {@link #dispatchOnDestinationChanged()}
*
* @param destinationId The topmost destination to retain
* @param inclusive Whether the given destination should also be popped.
*
* @return true if the stack was popped at least once, false otherwise
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) {
if (mBackStack.isEmpty()) {
// Nothing to pop if the back stack is empty
return false;
}
ArrayList<Navigator<?>> popOperations = new ArrayList<>();
Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
boolean foundDestination = false;
while (iterator.hasNext()) {
NavDestination destination = iterator.next().getDestination();
Navigator<?> navigator = mNavigatorProvider.getNavigator(
destination.getNavigatorName());
if (inclusive || destination.getId() != destinationId) {
popOperations.add(navigator);
}
if (destination.getId() == destinationId) {
foundDestination = true;
break;
}
}
if (!foundDestination) {
// We were passed a destinationId that doesn't exist on our back stack.
// Better to ignore the popBackStack than accidentally popping the entire stack
String destinationName = NavDestination.getDisplayName(mContext, destinationId);
Log.i(TAG, "Ignoring popBackStack to destination " + destinationName
+ " as it was not found on the current back stack");
return false;
}
boolean popped = false;
for (Navigator<?> navigator : popOperations) {
if (navigator.popBackStack()) {
NavBackStackEntry entry = mBackStack.removeLast();
entry.setMaxLifecycle(Lifecycle.State.DESTROYED);
if (mViewModel != null) {
mViewModel.clear(entry.mId);
}
popped = true;
} else {
// The pop did not complete successfully, so stop immediately
break;
}
}
updateOnBackPressedCallbackEnabled();
return popped;
}
而这个mBackStack是什么时候添加的navigator的呢?查看原始代码我们发现:
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();
}
}
还记得这个方法吗?我们一般手动切换Fragment时可以调用这个方法,最后就是追踪到这里。
findNavController().navigate(R.id.bottomNavSampleActivity)
同时,切换目标片段到栈顶。我们发现最后一个dispatchOnDestinationChanged()
方法,分配目标界面切换。有必要去跟一下,你可能会发现意想不到不到的东西:
/**
* Dispatch changes to all OnDestinationChangedListeners.
*
* If the back stack is empty, no events get dispatched.
*
* @return If changes were dispatched.
*/
private boolean dispatchOnDestinationChanged() {
// We never want to leave NavGraphs on the top of the stack
//noinspection StatementWithEmptyBody
while (!mBackStack.isEmpty()
&& mBackStack.peekLast().getDestination() instanceof NavGraph
&& popBackStackInternal(mBackStack.peekLast().getDestination().getId(), true)) {
// Keep popping
}
if (!mBackStack.isEmpty()) {
// First determine what the current resumed destination is and, if and only if
// the current resumed destination is a FloatingWindow, what destination is
// underneath it that must remain started.
NavDestination nextResumed = mBackStack.peekLast().getDestination();
NavDestination nextStarted = null;
if (nextResumed instanceof FloatingWindow) {
// Find the next destination in the back stack as that destination
// should still be STARTED when the FloatingWindow destination is above it.
Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
while (iterator.hasNext()) {
NavDestination destination = iterator.next().getDestination();
if (!(destination instanceof NavGraph)
&& !(destination instanceof FloatingWindow)) {
nextStarted = destination;
break;
}
}
}
// First iterate downward through the stack, applying downward Lifecycle
// transitions and capturing any upward Lifecycle transitions to apply afterwards.
// This ensures proper nesting where parent navigation graphs are started before
// their children and stopped only after their children are stopped.
HashMap<NavBackStackEntry, Lifecycle.State> upwardStateTransitions = new HashMap<>();
Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
while (iterator.hasNext()) {
NavBackStackEntry entry = iterator.next();
Lifecycle.State currentMaxLifecycle = entry.getMaxLifecycle();
NavDestination destination = entry.getDestination();
if (nextResumed != null && destination.getId() == nextResumed.getId()) {
// Upward Lifecycle transitions need to be done afterwards so that
// the parent navigation graph is resumed before their children
if (currentMaxLifecycle != Lifecycle.State.RESUMED) {
upwardStateTransitions.put(entry, Lifecycle.State.RESUMED);
}
nextResumed = nextResumed.getParent();
} else if (nextStarted != null && destination.getId() == nextStarted.getId()) {
if (currentMaxLifecycle == Lifecycle.State.RESUMED) {
// Downward transitions should be done immediately so children are
// paused before their parent navigation graphs
entry.setMaxLifecycle(Lifecycle.State.STARTED);
} else if (currentMaxLifecycle != Lifecycle.State.STARTED) {
// Upward Lifecycle transitions need to be done afterwards so that
// the parent navigation graph is started before their children
upwardStateTransitions.put(entry, Lifecycle.State.STARTED);
}
nextStarted = nextStarted.getParent();
} else {
entry.setMaxLifecycle(Lifecycle.State.CREATED);
}
}
// Apply all upward Lifecycle transitions by iterating through the stack again,
// this time applying the new lifecycle to the parent navigation graphs first
iterator = mBackStack.iterator();
while (iterator.hasNext()) {
NavBackStackEntry entry = iterator.next();
Lifecycle.State newState = upwardStateTransitions.get(entry);
if (newState != null) {
entry.setMaxLifecycle(newState);
} else {
// Ensure the state is up to date
entry.updateState();
}
}
// Now call all registered OnDestinationChangedListener instances
NavBackStackEntry backStackEntry = mBackStack.peekLast();
for (OnDestinationChangedListener listener :
mOnDestinationChangedListeners) {
listener.onDestinationChanged(this, backStackEntry.getDestination(),
backStackEntry.getArguments());
}
return true;
}
return false;
}
这里面发行了所有实现了OnDestinationChangedListener
接口的方法,继续跟踪,看看都如何实现了这个接口呢?
/**
* OnDestinationChangedListener receives a callback when the
* {@link #getCurrentDestination()} or its arguments change.
*/
public interface OnDestinationChangedListener {
/**
* Callback for when the {@link #getCurrentDestination()} or its arguments change.
* This navigation may be to a destination that has not been seen before, or one that
* was previously on the back stack. This method is called after navigation is complete,
* but associated transitions may still be playing.
*
* @param controller the controller that navigated
* @param destination the new destination
* @param arguments the arguments passed to the destination
*/
void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments);
}
只有一个类AbstractAppBarOnDestinationChangedListener实现了OnDestinationChangedListener 接口
,看一下具体实现:
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
if (destination instanceof FloatingWindow) {
return;
}
Openable openableLayout = mOpenableLayoutWeakReference != null
? mOpenableLayoutWeakReference.get()
: null;
if (mOpenableLayoutWeakReference != null && openableLayout == null) {
controller.removeOnDestinationChangedListener(this);
return;
}
CharSequence label = destination.getLabel();
if (label != null) {
// Fill in the data pattern with the args to build a valid URI
StringBuffer title = new StringBuffer();
Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");
Matcher matcher = fillInPattern.matcher(label);
while (matcher.find()) {
String argName = matcher.group(1);
if (arguments != null && arguments.containsKey(argName)) {
matcher.appendReplacement(title, "");
//noinspection ConstantConditions
title.append(arguments.get(argName).toString());
} else {
throw new IllegalArgumentException("Could not find " + argName + " in "
+ arguments + " to fill label " + label);
}
}
matcher.appendTail(title);
setTitle(title);
}
boolean isTopLevelDestination = NavigationUI.matchDestinations(destination,
mTopLevelDestinations);
if (openableLayout == null && isTopLevelDestination) {
setNavigationIcon(null, 0);
} else {
setActionBarUpIndicator(openableLayout != null && isTopLevelDestination);
}
}
原来如此,到这里就应该清楚了,当我们切换Fragment时,大概流程如下:
到这里,基本的几个核心类以及相关实现我们基本了解了,下面我们看一下基本的流程,首先我们从入口进去,一点点跟进
我们在最开始会初始化一个NavController:
@NonNull
public static NavController findNavController(@NonNull Activity activity, @IdRes int viewId) {
View view = ActivityCompat.requireViewById(activity, viewId);
NavController navController = findViewNavController(view);
if (navController == null) {
throw new IllegalStateException("Activity " + activity
+ " does not have a NavController set on " + viewId);
}
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) {
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;
}
查看代码可以看到是通过一个标签值来找到的,那么什么时候设置的呢?还记得NavHostFragment部分介绍的NavHostFragment的生命周期onViewCreated么?
@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);
// 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);
}
}
}
public static void setViewNavController(@NonNull View view,
@Nullable NavController controller) {
view.setTag(R.id.nav_controller_view_tag, controller);
}
NavController Naviagtion.setViewNavController()初始化好了之后,接下来将其和NavigationView,ToolBar,BottomNavigationView,DrawerLayout进行绑定:
不管是NavigationView还是Bottom``NavigationView,都会调用这个方法,他是AppCompatActivity的一个扩展方法,调用的是NavigationUI这个类:
public static void setupActionBarWithNavController(@NonNull AppCompatActivity activity,
@NonNull NavController navController,
@NonNull AppBarConfiguration configuration) {
navController.addOnDestinationChangedListener(
new ActionBarOnDestinationChangedListener(activity, configuration));
}
可以看到它就是调用了目标切换的那个接口,实现实现标题按钮等状态的改变。
我们看到它重载了很多方法,包括我们上面提到的NavigationView,ToolBar,BottomNavigationView,DrawerLayout。这样就将组件的状态切换绑定了,当片段切换时,上面提到的接口分配,去切换布局按钮等状态。
public static void setupWithNavController(
@NonNull final BottomNavigationView bottomNavigationView,
@NonNull final NavController navController) {
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
return onNavDestinationSelected(item, navController);
}
});
final WeakReference<BottomNavigationView> weakReference =
new WeakReference<>(bottomNavigationView);
navController.addOnDestinationChangedListener(
new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
BottomNavigationView view = weakReference.get();
if (view == null) {
navController.removeOnDestinationChangedListener(this);
return;
}
Menu menu = view.getMenu();
for (int h = 0, size = menu.size(); h < size; h++) {
MenuItem item = menu.getItem(h);
if (matchDestination(destination, item.getItemId())) {
item.setChecked(true);
}
}
}
});
}
public static void setupWithNavController(@NonNull final NavigationView navigationView,
@NonNull final NavController navController) {
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
boolean handled = onNavDestinationSelected(item, navController);
if (handled) {
ViewParent parent = navigationView.getParent();
if (parent instanceof Openable) {
((Openable) parent).close();
} else {
BottomSheetBehavior bottomSheetBehavior =
findBottomSheetBehavior(navigationView);
if (bottomSheetBehavior != null) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
}
}
return handled;
}
});
final WeakReference<NavigationView> weakReference = new WeakReference<>(navigationView);
navController.addOnDestinationChangedListener(
new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
NavigationView view = weakReference.get();
if (view == null) {
navController.removeOnDestinationChangedListener(this);
return;
}
Menu menu = view.getMenu();
for (int h = 0, size = menu.size(); h < size; h++) {
MenuItem item = menu.getItem(h);
item.setChecked(matchDestination(destination, item.getItemId()));
}
}
});
}
最后就是状态切换了,当点击菜单或目标片段切换的时候,改变状态。
遗留:还记得上面说的那个那个在设置菜单中的ID要和navigation.xml里片段的ID相同么?至于为什么要这样做,我们看上面的第一段代码:跟踪onNavDestinationSelected():
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
NavOptions.Builder builder = new NavOptions.Builder()
.setLaunchSingleTop(true);
if (navController.getCurrentDestination().getParent().findNode(item.getItemId())
instanceof ActivityNavigator.Destination) {
builder.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim);
} else {
builder.setEnterAnim(R.animator.nav_default_enter_anim)
.setExitAnim(R.animator.nav_default_exit_anim)
.setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
.setPopExitAnim(R.animator.nav_default_pop_exit_anim);
}
if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
}
NavOptions options = builder.build();
try {
//TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(item.getItemId(), null, options);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
我们看到最后还是调用 navigate() 方法,并且将MenuItem的ID作为参数传递过去:
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) {
NavDestination currentNode = mBackStack.isEmpty()
? mGraph
: mBackStack.getLast().getDestination();
if (currentNode == null) {
throw new IllegalStateException("no current navigation node");
}
@IdRes int destId = resId;
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;
}
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);
}
NavDestination node = findDestination(destId)通过菜单项的ID查询NavDestination:
NavDestination findDestination(@IdRes int destinationId) {
if (mGraph == null) {
return null;
}
if (mGraph.getId() == destinationId) {
return mGraph;
}
NavDestination currentNode = mBackStack.isEmpty()
? mGraph
: mBackStack.getLast().getDestination();
NavGraph currentGraph = currentNode instanceof NavGraph
? (NavGraph) currentNode
: currentNode.getParent();
return currentGraph.findNode(destinationId);
}
@Nullable
public final NavDestination findNode(@IdRes int resid) {
return findNode(resid, true);
}
@Nullable
final NavDestination findNode(@IdRes int resid, boolean searchParents) {
NavDestination destination = mNodes.get(resid);
// Search the parent for the NavDestination if it is not a child of this navigation graph
// and searchParents is true
return destination != null
? destination
: searchParents && getParent() != null ? getParent().findNode(resid) : null;
}
而mNodes是一个SparseArrayCompat数组,而NavDestination中维护了navigation.xml中的每个片段的相关信息:
在NavGraph.addDestination()初始化的时候通过放到矩阵mNodes中,而mId则就是我们的MenuItem的ID,所以很清楚了吧。
public final void addDestination(@NonNull NavDestination node) {
int id = node.getId();
if (id == 0) {
throw new IllegalArgumentException("Destinations must have an id."
+ " Call setId() or include an android:id in your navigation XML.");
}
if (id == getId()) {
throw new IllegalArgumentException("Destination " + node + " cannot have the same id "
+ "as graph " + this);
}
NavDestination existingDestination = mNodes.get(id);
if (existingDestination == node) {
return;
}
if (node.getParent() != null) {
throw new IllegalStateException("Destination already has a parent set."
+ " Call NavGraph.remove() to remove the previous parent.");
}
if (existingDestination != null) {
existingDestination.setParent(null);
}
node.setParent(this);
mNodes.put(node.getId(), node);
}
考虑到我们开始如果直接从setupWithNavController入口进行分析的话,可能不太容易找到怎么创建的图布局中的片段,以及NavHostFragment到底是什么,所以我们先分析了布局中的 NavHostFragment,我们发现为什么要在布局中声明了一个NavHostFragment,它是用来做什么的,最后发现在它的生命周期中创建了一个NavController,并且添加了FragmentNavigator,同时setGraph了。
紧接着我们通过setGraph进入到了NavController类中,通过graph里面设置的初始fragment看到了切换栈内部切换Fragment的代码。
在里面我们看到了熟悉的navigate()方法,在里面dispatchOnDestinationChanged()吸引了我的注意力,通过查找,发现切换FragmentAfter,通过该方法去改变布局的状态,也就是OnDestinationChangedListener接口。
到这里基本的代码实现已经了解的差不多了,然后我回到了入口,通过初始化NavController,调用NavigationUI中的方法绑定NavigationView,ToolBar,BottomNavigationView,DrawerLayout等布局,在调用navigate()方法后,改变状态,整个流程就走通了。
可能有一些不合理的地方,望大家见谅。
我们在Activity的布局里面设置了NavHostFragment,同时设置了navGraph布局,通过上面的分析我们知道NavHostFragment中新建了NavController,并且创建了管理日志片段事务并切换了FragmentNavigator,可以简单地把它理解成连接片段和NavController的一个主轴,同时也提供了包含导航的容器布局。
NavContorller是整个导航组件的核心,通过它来加载xml中片段转换成NavDestination,并保存在栈内,通过navigate()方法切换栈内NavDestination,以完成片段的切换操作。同时当片段切换后,下发OnDestinationChanged接口,来更改NavgationView,BottomNavgationView,Menu等相关UI操作。
通过NavgationUI类,为各个View设置接口监听,将View的UI状态和NavController中的切换片段制成绑定。