Android中導(dǎo)航組件Navigation的實現(xiàn)原理
對于導(dǎo)航組件的使用方式不是本文的重點,具體使用可以參考官方文檔,導(dǎo)航組件框架是通過fragment來實現(xiàn)的,其核心類主要可以分為三個NavGraph、NavHostController、NavHostFragment,這三個類的作用分別是:
NavGraph:
解析導(dǎo)航圖xml獲取到的對象,其內(nèi)部主要維護了一個集合用來存儲目的地,當(dāng)導(dǎo)航到目的地時,會傳遞進來一個id,這個id可能導(dǎo)航圖xml中fragment的id,也有可能是fragment節(jié)點下action節(jié)點的id,如果是action節(jié)點的id,內(nèi)部會轉(zhuǎn)換成fragment的id(這也就是說,action節(jié)點不加也是可以的),這樣就可以尋找到對應(yīng)的fragment。
NavHostController:
導(dǎo)航控制的核心類,內(nèi)部持有解析導(dǎo)航圖xml的對象,還維護了導(dǎo)航回退棧,管理著導(dǎo)航中的邏輯處理。
NavHostFragment:
導(dǎo)航組件的入口,主要是初始化一些相關(guān)類,最主要的是持有NavHostController,可以控制整個導(dǎo)航圖。
這里先看下在布局文件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的應(yīng)該知道,這里會去加載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導(dǎo)航圖,mGraphId是從onInflate()設(shè)置進來的 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); } } } /** * 創(chuàng)建導(dǎo)航控制器,在導(dǎo)航圖中,導(dǎo)航到的目的地可以是fragment、activity、dialog、子導(dǎo)航圖, * 導(dǎo)航到不同的目的地使用不同的控制器,此處提供的是dialog和fragment */ @SuppressWarnings({"WeakerAccess", "deprecation"}) @CallSuper protected void onCreateNavController(@NonNull NavController navController) { navController.getNavigatorProvider().addNavigator( new DialogFragmentNavigator(requireContext(), getChildFragmentManager())); navController.getNavigatorProvider().addNavigator(createFragmentNavigator()); } /** * 創(chuàng)建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這個類代碼行數(shù)不多,這里在精簡了下,保留了幾個在初始化流程上的方法,布局中遇到fragment標(biāo)簽,會先進行創(chuàng)建view,執(zhí)行到NavHostFragment就會先執(zhí)行這里的onInflate(),可以看到這里獲取到了導(dǎo)航圖的id,并賦值給了變量mGraphId。接著就會調(diào)用到fragment的生命周期方法,也就是這里的onCreate()方法,在這里會先初始化NavHostController對象,然后調(diào)用了onCreateNavController()方法,這個方法和NavHostController的構(gòu)造函數(shù)都創(chuàng)建了導(dǎo)航控制器并添加NavigatorProvider對象中,導(dǎo)航到指定頁面時用到的就是這里的控制器,之后調(diào)用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; // 在導(dǎo)航圖中配置的startDestination默認顯示頁面就是在這個方法中處理的 onGraphCreated(startDestinationArgs); }
可以看到,這里對導(dǎo)航圖xml進行了解析,最終結(jié)果存儲在NavGraph中,這里對xml的解析類似于布局xml的解析,這里就不進去看了,感興趣的可以自己看看,在導(dǎo)航圖的根標(biāo)簽下通常會配置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(); } }
這里會調(diào)用到navigate()這個方法,傳遞的是導(dǎo)航圖中的根對象:
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); ... ... }
這里先獲取到導(dǎo)航控制器,然后導(dǎo)航到對應(yīng)的界面,關(guān)于導(dǎo)航控制器的添加,前面有說到,這里再來看下具體的添加:
public NavController(@NonNull Context context) { ... ... mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider)); mNavigatorProvider.addNavigator(new ActivityNavigator(mContext)); }
調(diào)用的是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是導(dǎo)航控制器類上的注解,比如:
@Navigator.Name("navigation") public class NavGraphNavigator extends Navigator<NavGraph> { ... ... }
這里獲取到的name就是這個navigation,并以這個name為key保存對應(yīng)的導(dǎo)航控制器,這里回到上面的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是導(dǎo)航圖的根對象,node.getNavigatorName()獲取到的值是navigation,故這里獲取到的導(dǎo)航控制器是NavGraphNavigator,接著調(diào)用它的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); }
先獲取到導(dǎo)航圖中配置的默認顯示視圖id,然后根據(jù)id找到對應(yīng)的導(dǎo)航目的地,根據(jù)導(dǎo)航目的地獲取對應(yīng)導(dǎo)航控制器,以如下導(dǎo)航圖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,那對應(yīng)的導(dǎo)航控制器是FragmentNavigator,接著調(diào)用它的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的操作了,執(zhí)行完成后對應(yīng)的視圖也就顯示出來了,關(guān)于點擊導(dǎo)航的也類似,這里就不在贅述了。
到此這篇關(guān)于Android中導(dǎo)航組件Navigation的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Android導(dǎo)航組件Navigation內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android控件系列之RadioButton與RadioGroup使用方法
本文介紹了Android中如何使用RadioGroup和RadioButton,對比了RadioButton和CheckBox的區(qū)別,并實現(xiàn)了自定義的RadioGroup中被選中RadioButton的變更監(jiān)聽事件2012-11-11Android App中制作仿MIUI的Tab切換效果的實例分享
這篇文章主要介紹了Android App中制作仿MIUI的Tab切換效果的實例分享,實現(xiàn)具有跟隨手指滾動而滾動功能的ViewPagerIndicator,需要的朋友可以參考下2016-04-04