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时,大概流程如下:

  1. 切换目标fragment到栈顶
  2. 分发目标片段切换状态
  3. 设置工具栏的标题,icon状态等
  4. 当然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);
}

总结

流程

  1. 考虑到我们开始如果直接从setupWithNavController入口进行分析的话,可能不太容易找到怎么创建的图布局中的片段,以及NavHostFragment到底是什么,所以我们先分析了布局中的 NavHostFragment,我们发现为什么要在布局中声明了一个NavHostFragment,它是用来做什么的,最后发现在它的生命周期中创建了一个NavController,并且添加了FragmentNavigator,同时setGraph了。

  2. 紧接着我们通过setGraph进入到了NavController类中,通过graph里面设置的初始fragment看到了切换栈内部切换Fragment的代码。

  3. 在里面我们看到了熟悉的navigate()方法,在里面dispatchOnDestinationChanged()吸引了我的注意力,通过查找,发现切换FragmentAfter,通过该方法去改变布局的状态,也就是OnDestinationChangedListener接口。

  4. 到这里基本的代码实现已经了解的差不多了,然后我回到了入口,通过初始化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中的切换片段制成绑定。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值