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默認(rèn)顯示頁面就是在這個方法中處理的
onGraphCreated(startDestinationArgs);
}可以看到,這里對導(dǎo)航圖xml進行了解析,最終結(jié)果存儲在NavGraph中,這里對xml的解析類似于布局xml的解析,這里就不進去看了,感興趣的可以自己看看,在導(dǎo)航圖的根標(biāo)簽下通常會配置startDestination屬性指定啟動的默認(rèn)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)航圖中配置的默認(rèn)顯示視圖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-11
Android App中制作仿MIUI的Tab切換效果的實例分享
這篇文章主要介紹了Android App中制作仿MIUI的Tab切換效果的實例分享,實現(xiàn)具有跟隨手指滾動而滾動功能的ViewPagerIndicator,需要的朋友可以參考下2016-04-04

