Jetpack Navigation----原始碼解析
Jetpack Navigation----原始碼解析
原始解析
NavHostFragment
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.
*
* <p>The inflated graph can be retrieved via {@link #getGraph()}.</p>
*
* @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.
*
* <p>The graph can be retrieved later via {@link #getGraph()}.</p>
*
* @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
* <strong>not</strong> 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.
* <p>
* 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時,大概流程如下:
- 切換目標fragment到棧頂
- 分發目標片段切換狀態
- 設定工具欄的標題,icon狀態等
- 當然setTitle(),setNavigationIcon()等都為抽象方法,具體實現可以看子類裡是怎麼實現的,具體就不敘述了
到這裡,基本的幾個核心類以及相關實現我們基本瞭解了,下面我們看一下基本的流程,首先我們從入口進去,一點點跟進
Navigation.findNavController(this,R.id.xxx)
我們在最開始會初始化一個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進行繫結:
NavigationUI.setupActionBarWithNavController
不管是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。這樣就將元件的狀態切換繫結了,當片段切換時,上面提到的介面分配,去切換佈局按鈕等狀態。
NavigationUI.setupWithNavController
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()方法後,改變狀態,整個流程就走通了。
可能有一些不合理的地方,望大家見諒。
類圖
分析
NavHostFragment
我們在Activity的佈局裡面設定了NavHostFragment,同時設定了navGraph佈局,通過上面的分析我們知道NavHostFragment中新建了NavController,並且建立了管理日誌片段事務並切換了FragmentNavigator,可以簡單地把它理解成連線片段和NavController的一個主軸,同時也提供了包含導航的容器佈局。
導航控制器
NavContorller是整個導航元件的核心,通過它來載入xml中片段轉換成NavDestination,並儲存在棧內,通過navigate()方法切換棧內NavDestination,以完成片段的切換操作。同時當片段切換後,下發OnDestinationChanged介面,來更改NavgationView,BottomNavgationView,Menu等相關UI操作。
NavigationUI
通過NavgationUI類,為各個View設定介面監聽,將View的UI狀態和NavController中的切換片段製成繫結。
相關文章
- 2. Jetpack原始碼解析---Navigation為什麼切換Fragment會重繪?Jetpack原始碼NavigationFragment
- Android Jetpack系列——ViewModel原始碼分析AndroidJetpackView原始碼
- Jetpack系列 - Lifecycle從入門到原始碼Jetpack原始碼
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- 深度解析 Jetpack Compose 佈局Jetpack
- Spark原始碼-SparkContext原始碼解析Spark原始碼Context
- CountDownLatch原始碼解析CountDownLatch原始碼
- LeakCanary原始碼解析原始碼
- vuex原始碼解析Vue原始碼
- ArrayBlockQueue原始碼解析BloC原始碼
- AsyncTask原始碼解析原始碼
- CopyOnWriteArrayList原始碼解析原始碼
- Express原始碼解析Express原始碼
- Observer原始碼解析Server原始碼
- SparseArray 原始碼解析原始碼
- RecyclerView原始碼解析View原始碼
- Promise 原始碼解析Promise原始碼
- Koa原始碼解析原始碼
- RateLimiter原始碼解析MIT原始碼
- redux原始碼解析Redux原始碼
- SDWebImage原始碼解析Web原始碼
- CyclicBarrier原始碼解析原始碼
- Semaphore原始碼解析原始碼
- Exchanger原始碼解析原始碼
- AbstractQueuedSynchronizer原始碼解析原始碼
- OKio原始碼解析原始碼
- Koa 原始碼解析原始碼
- RxPermission原始碼解析原始碼
- MyBatis原始碼解析MyBatis原始碼
- ArrayList原始碼解析原始碼
- Aspects 原始碼解析原始碼
- LeakCanary 原始碼解析原始碼
- Vue原始碼解析Vue原始碼
- React原始碼解析React原始碼
- ButterKnife原始碼解析原始碼
- HashSet原始碼解析原始碼
- Retrofit 原始碼解析原始碼
- Javapoet原始碼解析Java原始碼