Android导航组件Navigation
tangedegushi 人气:0对于导航组件的使用方式不是本文的重点,具体使用可以参考官方文档,导航组件框架是通过fragment来实现的,其核心类主要可以分为三个NavGraph、NavHostController、NavHostFragment,这三个类的作用分别是:
NavGraph:
解析导航图xml获取到的对象,其内部主要维护了一个集合用来存储目的地,当导航到目的地时,会传递进来一个id,这个id可能导航图xml中fragment的id,也有可能是fragment节点下action节点的id,如果是action节点的id,内部会转换成fragment的id(这也就是说,action节点不加也是可以的),这样就可以寻找到对应的fragment。
NavHostController:
导航控制的核心类,内部持有解析导航图xml的对象,还维护了导航回退栈,管理着导航中的逻辑处理。
NavHostFragment:
导航组件的入口,主要是初始化一些相关类,最主要的是持有NavHostController,可以控制整个导航图。
这里先看下在布局文件xml中的简单使用:
<fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:layout_constraintBottom_toTopOf="@id/nav_view" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/mobile_navigation" />
这里的name属性指定了androidx.navigation.fragment.NavHostFragment,熟悉fragment的应该知道,这里会去加载NavHostFragment,
public class NavHostFragment extends Fragment implements NavHost { private static final String KEY_GRAPH_ID = "android-support-nav:fragment:graphId"; private static final String KEY_START_DESTINATION_ARGS = "android-support-nav:fragment:startDestinationArgs"; private static final String KEY_NAV_CONTROLLER_STATE = "android-support-nav:fragment:navControllerState"; private static final String KEY_DEFAULT_NAV_HOST = "android-support-nav:fragment:defaultHost"; private NavHostController mNavController; private Boolean mIsPrimaryBeforeOnCreate = null; private View mViewParent; // State that will be saved and restored private int mGraphId; private boolean mDefaultNavHost; @CallSuper @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(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) { // 会去解析xml导航图,mGraphId是从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); } } } /** * 创建导航控制器,在导航图中,导航到的目的地可以是fragment、activity、dialog、子导航图, * 导航到不同的目的地使用不同的控制器,此处提供的是dialog和fragment */ @SuppressWarnings({"WeakerAccess", "deprecation"}) @CallSuper protected void onCreateNavController(@NonNull NavController navController) { navController.getNavigatorProvider().addNavigator( new DialogFragmentNavigator(requireContext(), getChildFragmentManager())); navController.getNavigatorProvider().addNavigator(createFragmentNavigator()); } /** * 创建fragment的控制器 */ @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated @NonNull protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() { return new FragmentNavigator(requireContext(), getChildFragmentManager(), getContainerId()); } @CallSuper @Override public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs, @Nullable Bundle savedInstanceState) { super.onInflate(context, attrs, savedInstanceState); final TypedArray navHost = context.obtainStyledAttributes(attrs, androidx.navigation.R.styleable.NavHost); final int graphId = navHost.getResourceId( androidx.navigation.R.styleable.NavHost_navGraph, 0); if (graphId != 0) { mGraphId = graphId; } ... ... } }
NavHostFragment这个类代码行数不多,这里在精简了下,保留了几个在初始化流程上的方法,布局中遇到fragment标签,会先进行创建view,执行到NavHostFragment就会先执行这里的onInflate(),可以看到这里获取到了导航图的id,并赋值给了变量mGraphId。接着就会调用到fragment的生命周期方法,也就是这里的onCreate()方法,在这里会先初始化NavHostController对象,然后调用了onCreateNavController()方法,这个方法和NavHostController的构造函数都创建了导航控制器并添加NavigatorProvider对象中,导航到指定页面时用到的就是这里的控制器,之后调用mNavController.setGraph(mGraphId):
public void setGraph(@NavigationRes int graphResId) { setGraph(graphResId, null); } @CallSuper public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) { setGraph(getNavInflater().inflate(graphResId), startDestinationArgs); } @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; // 在导航图中配置的startDestination默认显示页面就是在这个方法中处理的 onGraphCreated(startDestinationArgs); }
可以看到,这里对导航图xml进行了解析,最终结果存储在NavGraph中,这里对xml的解析类似于布局xml的解析,这里就不进去看了,感兴趣的可以自己看看,在导航图的根标签下通常会配置startDestination属性指定启动的默认fragment,对这个属性的处理就在onGraphCreate()方法中:
private void onGraphCreated(@Nullable Bundle startDestinationArgs) { ... ... if (mGraph != null && mBackStack.isEmpty()) { boolean deepLinked = !mDeepLinkHandled && mActivity != null && handleDeepLink(mActivity.getIntent()); if (!deepLinked) { // Navigate to the first destination in the graph // if we haven't deep linked to a destination navigate(mGraph, startDestinationArgs, null, null); } } else { dispatchOnDestinationChanged(); } }
这里会调用到navigate()这个方法,传递的是导航图中的根对象:
private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { ... ... Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator( node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras); ... ... }
这里先获取到导航控制器,然后导航到对应的界面,关于导航控制器的添加,前面有说到,这里再来看下具体的添加:
public NavController(@NonNull Context context) { ... ... mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider)); mNavigatorProvider.addNavigator(new ActivityNavigator(mContext)); }
调用的是NavigatorProvider的addNavigator()方法:
private final HashMap<String, Navigator<? extends NavDestination>> mNavigators = new HashMap<>(); public final Navigator<? extends NavDestination> addNavigator( @NonNull Navigator<? extends NavDestination> navigator) { String name = getNameForNavigator(navigator.getClass()); return addNavigator(name, navigator); } @CallSuper @Nullable public Navigator<? extends NavDestination> addNavigator(@NonNull String name, @NonNull Navigator<? extends NavDestination> navigator) { if (!validateName(name)) { throw new IllegalArgumentException("navigator name cannot be an empty string"); } return mNavigators.put(name, navigator); }
这里拿到的name是导航控制器类上的注解,比如:
@Navigator.Name("navigation") public class NavGraphNavigator extends Navigator<NavGraph> { ... ... }
这里获取到的name就是这个navigation,并以这个name为key保存对应的导航控制器,这里回到上面的navigate()方法:
private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { ... ... Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator( node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras); ... ... }
传入的node是导航图的根对象,node.getNavigatorName()获取到的值是navigation,故这里获取到的导航控制器是NavGraphNavigator,接着调用它的navigate()方法:
public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) { int startId = destination.getStartDestination(); ... ... NavDestination startDestination = destination.findNode(startId, false); ... ... Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator( startDestination.getNavigatorName()); return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args), navOptions, navigatorExtras); }
先获取到导航图中配置的默认显示视图id,然后根据id找到对应的导航目的地,根据导航目的地获取对应导航控制器,以如下导航图xml为例:
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/mobile_navigation" app:startDestination="@id/navigation_home"> <fragment android:id="@+id/navigation_home" android:name="com.tangedegushi.jetpack_navigation.ui.home.HomeFragment" android:label="@string/title_home" tools:layout="@layout/fragment_home" /> </navigation>
startDestination.getNavigatorName()获取到就是fragment,那对应的导航控制器是FragmentNavigator,接着调用它的navigate()方法:
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { if (mFragmentManager.isStateSaved()) { Log.i(TAG, "Ignoring navigate() call: FragmentManager has already" + " saved its state"); return null; } String className = destination.getClassName(); if (className.charAt(0) == '.') { className = mContext.getPackageName() + className; } final Fragment frag = instantiateFragment(mContext, mFragmentManager, className, args); frag.setArguments(args); final FragmentTransaction ft = mFragmentManager.beginTransaction(); int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1; int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1; int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1; int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1; if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) { enterAnim = enterAnim != -1 ? enterAnim : 0; exitAnim = exitAnim != -1 ? exitAnim : 0; popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0; popExitAnim = popExitAnim != -1 ? popExitAnim : 0; ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim); } ft.replace(mContainerId, frag); ft.setPrimaryNavigationFragment(frag); final @IdRes int destId = destination.getId(); final boolean initialNavigation = mBackStack.isEmpty(); // TODO Build first class singleTop behavior for fragments final boolean isSingleTopReplacement = navOptions != null && !initialNavigation && navOptions.shouldLaunchSingleTop() && mBackStack.peekLast() == destId; boolean isAdded; if (initialNavigation) { isAdded = true; } else if (isSingleTopReplacement) { // Single Top means we only want one instance on the back stack if (mBackStack.size() > 1) { // If the Fragment to be replaced is on the FragmentManager's // back stack, a simple replace() isn't enough so we // remove it from the back stack and put our replacement // on the back stack in its place mFragmentManager.popBackStack( generateBackStackName(mBackStack.size(), mBackStack.peekLast()), FragmentManager.POP_BACK_STACK_INCLUSIVE); ft.addToBackStack(generateBackStackName(mBackStack.size(), destId)); } isAdded = false; } else { ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId)); isAdded = true; } if (navigatorExtras instanceof Extras) { Extras extras = (Extras) navigatorExtras; for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) { ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue()); } } ft.setReorderingAllowed(true); ft.commit(); // The commit succeeded, update our view of the world if (isAdded) { mBackStack.add(destId); return destination; } else { return null; } }
这里就是对fragment的操作了,执行完成后对应的视图也就显示出来了,关于点击导航的也类似,这里就不在赘述了。
加载全部内容