个人博客
http://www.milovetingting.cn
Jetpack学习-Navigation Navigation是什么 Navigation翻译过来就是导航。
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。
导航组件由以下三个关键部分组成:
导航图:在一个集中位置包含所有导航相关信息的 XML 资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。
NavHost:显示导航图中目标的空白容器。导航组件包含一个默认 NavHost 实现 (NavHostFragment),可显示 Fragment 目标。
NavController:在 NavHost 中管理应用导航的对象。当用户在整个应用中移动时,NavController 会安排 NavHost 中目标内容的交换。
在应用中导航时,您告诉 NavController,您想沿导航图中的特定路径导航至特定目标,或直接导航至特定目标。NavController 便会在 NavHost 中显示相应目标。
导航组件提供各种其他优势,包括以下内容:
处理 Fragment 事务。
默认情况下,正确处理往返操作。
为动画和转换提供标准化资源。
实现和处理深层链接。
包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。
Safe Args - 可在目标之间导航和传递数据时提供类型安全的 Gradle 插件。
ViewModel 支持 - 您可以将 ViewModel 的范围限定为导航图,以在图表的目标之间共享与界面相关的数据。
此外,您还可以使用 Android Studio 的 Navigation Editor 来查看和编辑导航图。
以上内容来自官方文档(我只是一个搬运工\(^o^)/)
简单使用 引入Navigation 在需要使用Navigation的模块的build.gradle中引入
1 2 3 def nav_version = "2.3.0-alpha01" implementation "androidx.navigation:navigation-fragment:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version"
建立导航图 在res目录右键-New-Android Resource File
在弹出的界面中,File name可随意输入,Resource type选择Navigation,点击确定
点击确定后,会在res目录下创建navigation目录,以及刚才定义的导航文件
双击打开刚才创建的导航文件,在Design界面可以看到目前还没有内容,可以点击上方的+号图标添加fragment,也可以自己手动在xml中添加
我们需要为这个文件指定startDestination,即起始的界面
startDestination指定为mainFragment,mainFragment对应的布局为fragment_main
Navigation首先会加载一个默认的Fragment,这个需要在Activity中指定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" android:layout_width ="match_parent" android:layout_height ="match_parent" > <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:navGraph ="@navigation/nav_graph" /> </LinearLayout >
配置defaultNavHost为true,即指定这个fragment为默认的NavHost,每个Activity只能指定一个默认的NavHost。这里的name
配置为androidx.navigation.fragment.NavHostFragment
,navGraph
配置为nav_graph,即指定nav_graph为导航图。这样当Activity启动时,会首先通过activity布局里的fragment去加载导航图中的startDestination配置的fragment。
导航 通过一个Fragment导航到另一个Fragment,可以通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_main, container, false ); loginBtn = view.findViewById(R.id.fragment_main_login); loginBtn.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { Bundle bundle = new Bundle (); bundle.putString("name" , "zs" ); Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_loginFragment, bundle); } }); return view; }
这里通过点击一个按钮进行跳转,通过Navigation.findNavController(v).navigate()
方法导航。这里还可以通过Bundle进行传值。
在目的Fragment,可以通过getArguments()
来获取到传递过来的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { String name = getArguments().getString("name" , "null" ); Toast.makeText(getContext(), name, Toast.LENGTH_SHORT).show(); View view = inflater.inflate(R.layout.fragment_login, container, false ); backBtn = view.findViewById(R.id.fragment_login_back); backBtn.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { Navigation.findNavController(v).popBackStack(); } }); return view; }
在目的Fragment,还可以通过一个按钮返回上一个Fragment:Navigation.findNavController(v).popBackStack()
原理 Navigation的简单使用流程就介绍到这,可以在官方文档上看更多相关的使用方法。下面来分析下Navigation的流程
显示起始Fragment 在Activity启动时,会先实例化NavHostFragment
,这个是我们前面在布局中指定的。
首先会执行onInflate
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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; } navHost.recycle(); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment); final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false ); if (defaultHost) { mDefaultNavHost = true ; } a.recycle(); }
这个方法,其实就是解析出来我们要使用哪个导航图,获取到了graphId。还获取了是否为默认的Host:defaultHost
然后会执行onAttach
方法
1 2 3 4 5 6 7 8 9 10 11 public void onAttach (@NonNull Context context) { super .onAttach(context); if (mDefaultNavHost) { getParentFragmentManager().beginTransaction() .setPrimaryNavigationFragment(this ) .commit(); } }
由于在onInflate已经获取到mDefaultNavHost为true,因此这里会将当前Fragment通过commit加入到FragmentManager()中
然后执行onCreate
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public void onCreate (@Nullable Bundle savedInstanceState) { super .onCreate(savedInstanceState); final Context context = requireContext(); mNavController = new NavHostController (context); mNavController.setLifecycleOwner(this ); mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher()); 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 ) { mNavController.restoreState(navState); } if (mGraphId != 0 ) { mNavController.setGraph(mGraphId); } else { 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); } } }
在这个方法里,设置了NavHostController及NavigatorProvider,然后执行NavController.setGraph
方法
1 2 3 public void setGraph (@NavigationRes int graphResId) { setGraph(graphResId, null ); }
继续调用setGraph
1 2 3 public void setGraph (@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) { setGraph(getNavInflater().inflate(graphResId), startDestinationArgs); }
继续调用setGraph
1 2 3 4 5 6 7 8 public void setGraph (@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) { if (mGraph != null ) { popBackStackInternal(mGraph.getId(), true ); } mGraph = graph; onGraphCreated(startDestinationArgs); }
调用onGraphCreated
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 private void onGraphCreated (@Nullable Bundle startDestinationArgs) { if (mNavigatorStateToRestore != null ) { ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList( KEY_NAVIGATOR_STATE_NAMES); if (navigatorNames != null ) { for (String name : navigatorNames) { Navigator<?> navigator = mNavigatorProvider.getNavigator(name); Bundle bundle = mNavigatorStateToRestore.getBundle(name); if (bundle != null ) { navigator.onRestoreState(bundle); } } } } if (mBackStackToRestore != null ) { for (Parcelable parcelable : mBackStackToRestore) { NavBackStackEntryState state = (NavBackStackEntryState) parcelable; NavDestination node = findDestination(state.getDestinationId()); if (node == null ) { throw new IllegalStateException ("unknown destination during restore: " + mContext.getResources().getResourceName(state.getDestinationId())); } Bundle args = state.getArgs(); if (args != null ) { args.setClassLoader(mContext.getClassLoader()); } NavBackStackEntry entry = new NavBackStackEntry (mContext, node, args, mLifecycleOwner, mViewModel, state.getUUID(), state.getSavedState()); mBackStack.add(entry); } updateOnBackPressedCallbackEnabled(); mBackStackToRestore = null ; } if (mGraph != null && mBackStack.isEmpty()) { boolean deepLinked = !mDeepLinkHandled && mActivity != null && handleDeepLink(mActivity.getIntent()); if (!deepLinked) { navigate(mGraph, startDestinationArgs, null , null ); } } }
首次进入Activity,会最终执行navigate(mGraph, startDestinationArgs, null, null)
方法来导航到起始的目的Fragment
1 2 3 4 5 6 7 8 9 10 11 private void navigate (@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { boolean popped = false ; Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator( node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras); }
这个方法,会调用navigator.navigate(node, finalArgs,navOptions, navigatorExtras)
方法,这个方法的实现在NavGraphNavigator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public NavDestination navigate (@NonNull NavGraph destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) { int startId = destination.getStartDestination(); if (startId == 0 ) { throw new IllegalStateException ("no start destination defined via" + " app:startDestination for " + destination.getDisplayName()); } NavDestination startDestination = destination.findNode(startId, false ); if (startDestination == null ) { final String dest = destination.getStartDestDisplayName(); throw new IllegalArgumentException ("navigation destination " + dest + " is not a direct child of this NavGraph" ); } Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator( startDestination.getNavigatorName()); return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args), navOptions, navigatorExtras); }
在这个方法里调用navigator.navigate方法,这个方法实现在FragmentNavigator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 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(); final boolean isSingleTopReplacement = navOptions != null && !initialNavigation && navOptions.shouldLaunchSingleTop() && mBackStack.peekLast() == destId; boolean isAdded; if (initialNavigation) { isAdded = true ; } else if (isSingleTopReplacement) { if (mBackStack.size() > 1 ) { 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(); if (isAdded) { mBackStack.add(destId); return destination; } else { return null ; } }
在这个方法中,通过反射实例化目的Fragment,然后replace原来的Fragment,并commit,这样目的Fragment就显示出来了。
导航到其它Fragment 通过Navigation.findNavController(v).navigate(resId)
可以导航到指定的Fragment
1 2 3 4 5 6 7 public static NavController findNavController (@NonNull View view) { NavController navController = findViewNavController(view); if (navController == null ) { throw new IllegalStateException ("View " + view + " does not have a NavController set" ); } return navController; }
然后调用findViewNavController
1 2 3 4 5 6 7 8 9 10 11 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 ; }
这里通过子View往父View不停查找NavController,这个NavController在前面onCreate的时候已经附加到了view上。
找到NavController后,调用navigate。这个过程和前面第一次导航到起始Fragment是一样的流程,这里不再分析。
其实这里只是比较粗的一个梳理,涉及很多细节并没有具体去看,暂且先有一个流程的印象吧。
附上一张时序图