建議感興趣的能夠按照本篇文章的思路看一下源碼,閱讀源碼過程當中遇到的問題基本在文章中均已提到。java
以前已經翻譯過了Google官方的CodeLabs上面的教程,教程很詳細,代碼在Github上也能夠找到,本篇文章旨在本身的APP上使用效果及演示Demo,來具體的使用Navigation。而且對其進行源碼解析。node
基本相關介紹能夠查看我以前翻譯的文章,基本就是google翻譯了一個大概。android
1、Android Jetpack_Note_CodeLabs一Navigationgit
雖然在以前的文章中已經很詳細的介紹了
Navigation
,可是這裏也簡單的敘述一下我在項目中的具體使用:github
咱們能夠經過使用Navigation
配合DrawerLayout
側邊欄和Toolbar
標題來進行工做,再也不須要咱們去定義點擊事件,也不須要咱們去管理Fragment作切換,只須要咱們作相關的配置和極少許的代碼就能夠了。web
側邊欄的用法和咱們以前的使用同樣,配置好咱們NavigationView
裏面的_headerLayout_
、_menu_
便可;設計模式
**注意:**這裏面的menu有一點和咱們以前的不同,item的id必需要和navigation裏面的fragment的id相同,不然點擊事件不生效,這裏先提一下,下面會詳細介紹。數組
DrawerLayout
配置好以後,咱們再來配置標題欄,以前咱們的用法都是在中間加一個存放Fragment
的容器,有多是FrameLayout
、ViewPager
等,這裏面咱們須要配置一個Fragment
,這個Fragment
的name是androidx.navigation.fragment.NavHostFragment
,這是一個添加到佈局中的特殊部件,NavHostFragment經過navGraph與navigation導航編輯器進行關聯。具體代碼以下:app
<androidx.drawerlayout.widget.DrawerLayout xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start">
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout android:layout_height="wrap_content" android:layout_width="match_parent" android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<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"/>
</LinearLayout>
<com.google.android.material.navigation.NavigationView app:itemIconTint="@color/nav_item_txt" app:itemTextColor="@color/nav_item_txt" android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/nav_header_main" app:menu="@menu/activity_main_drawer"/>
</androidx.drawerlayout.widget.DrawerLayout>
複製代碼
咱們能夠看到NavHostFragment中有兩個屬性比較特殊:app:defaultNavHost
和app:navGraph="@navigation/navigation_main"
,前者就是是不是默認的其實頁面,後者就是咱們要設計的Navigation佈局文件.編輯器
在Android Studio
3.2版本以上裏面內嵌了Navigation
的設計面板工具,咱們能夠在res文件夾下面的navigation
文件裏面對咱們的fragment/Activity進行設計。
打開Desgin面板,進入設計模式,在裏面咱們能夠新建咱們的目標頁面。若是你還沒建立過一個**Destination,**你能夠點擊create a destination
建立一個Fragmengt/Activity
。固然若是你以前已經建立好了的話,在這裏你能夠直接選擇:
選擇完一個Destination以後,在面板中就能夠看到了,具體的action、arguments就不介紹了,詳細的能夠看以前的文章。
打開Text模式的xml咱們能夠看到咱們選擇的Fragmengt配置信息,固然你也能夠不經過面板設計,也能夠直接在xml裏進行代碼編寫。startDestination
是APP默認啓動的頁面,這裏面必需要指定,不然會報錯crash。這裏個人代碼所指默認頁面是HomeFragment
,以下:
<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/navigation_main" app:startDestination="@+id/homeFragment" tools:ignore="UnusedNavigation">
<fragment android:id="@+id/homeFragment" android:name="com.hankkin.jetpack_note.ui.home.HomeFragment" android:label="@string/menu_home">
<action android:id="@+id/action_navigationFragment_to_webFragment" app:destination="@id/webFragment" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right"/>
</fragment>
<fragment android:id="@+id/codeFragment" android:name="com.hankkin.jetpack_note.ui.CodeFragment" android:label="@string/menu_code"/>
複製代碼
咱們能夠看到上面的佈局代碼 默認的起始頁面是homeFragment,下面還有一個codeFragment,其實這兩個fragment也就是對應着在menu中的兩個菜單,同時也對應咱們側邊欄中的一個首頁和一個代碼頁,
<item android:id="@+id/homeFragment" android:icon="@drawable/ic_menu_home" android:title="@string/menu_home"/>
<item android:id="@+id/codeFragment" android:icon="@drawable/ic_menu_code" android:title="@string/menu_code"/>
複製代碼
還記得上面說的id要相同嗎?就是上面item的id要和navigation_main.xml中fragment的id相同,不然點擊菜單不會切換fragment的。
配置完上面這些信息以後,怎麼將他們綁定起來使用呢?
先看下代碼:
navController = Navigation.findNavController(this, R.id.fragment_home)
appBarConfiguration = AppBarConfiguration(setOf(R.id.homeFragment, R.id.codeFragment), drawerLayout)
// Set up ActionBar
setSupportActionBar(mDataBinding.toolbar)
setupActionBarWithNavController(navController, appBarConfiguration)
// Set up navigation menu
mDataBinding.navView.setupWithNavController(navController)
複製代碼
到此,咱們的基本配置就結束了,能夠看到咱們drawerlayout中的首頁和代碼按鈕點擊會切換對應的fragment,同時toolbar的漢堡按鈕和返回按鈕也會自動切換;固然Navigation還能夠配合BottomNavigationView使用。
和上面的步驟相似:也是配置好 navigation.xml佈局以及 BottomNavigationView所對應的menu菜單文件
固然BottomNavigationView也提供了擴展方法setupWithNavController去綁定菜單和fragment,這裏使用很簡單就不具體介紹了。詳情可見BottomNavSampleActivity。
先看一下navigation的Desgin模式:
可能你會注意到這些線是什麼?沒錯這就是一個一個的Action,當你手動將兩個Fragment進行連線後,在xml佈局裏面會對應生成一個標籤,例如:
<action android:id="@+id/action_dashBoardSampleFragment_to_notificationSampleFragment" app:destination="@id/notificationSampleFragment"/>
複製代碼
它會自動建立好id,id有可能比較長,可是確很清楚,從xtoy的模式,固然若是你不喜歡能夠本身改,destination則是咱們要跳轉到的目標接界面。
action設置好了以後,咱們能夠執行下面代碼進行跳轉:
findNavController().navigate(R.id.action_homeSampleFragment_to_dashBoardSampleFragment_action)
複製代碼
固然fragment之間的切換是支持動畫的,NavOptions是一個動畫管理類,咱們能夠設置進入和回退的動畫,設置的方式有兩種:
<action android:id="@+id/action_homeSampleFragment_to_dashBoardSampleFragment_action" app:destination="@id/dashBoardSampleFragment" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right"/>
複製代碼
val options = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
view.findViewById<Button>(R.id.navigate_destination_button)?.setOnClickListener {
findNavController().navigate(R.id.flow_step_one_dest, null, options)
}
複製代碼
fragment之間的切換參數傳遞的方法也很簡單,以前咱們可能要經過宿主Activity或者接口等方法,總之挺麻煩的,下面咱們看看經過Navigation控制的Fragment之間怎麼傳遞?
咱們能夠在naviagtion佈局中使用標籤,
<argument android:name="deep_args" app:argType="" android:defaultValue="" app:nullable=""/>
複製代碼
**注意:**固然type類型也支持咱們自定的實體類,可是須要你填寫類的全路徑,同時你要保證明體類實現了序列化
咱們能夠經過把參數傳遞封裝到Bundle中,而後再執行navigate()方法時傳遞過去,例如:
val args = Bundle()
args.putString("link","1")
args.putString("title","1")
it.findNavController().navigate(R.id.webFragment, args)
複製代碼
固然你在接受是也能夠經過getArguments().getString(xxxx)這種方式去獲取,可是Navigation組件還提供給了咱們更簡單的方式,當你設置了標籤後,經過編譯代碼,會自動爲咱們生成一個XXXFragmentDirections類,它裏面爲咱們做了參數的封裝,而NavController的navigate()方法同時支持direction類型的傳遞。
val direction = HomeFragmentDirections.actionNavigationFragmentToWebFragment(link,title)
it.findNavController().navigate(direction)
複製代碼
同時在咱們的目標頁面所對應了一個XXXFragmentArgs,咱們能夠直接拿到navArgs()從這裏咱們能夠直接拿到參數。
private val args: WebFragmentArgs by navArgs()
複製代碼
關於Deep Link 是指跳入應用內的一個功能,我就把它翻譯成深層連接了,Navigation提供了這樣一個功能,使用起來也很簡單:
val args = Bundle()
args.putString("deep_args",et_deep_link.text.toString())
val deep = findNavController().createDeepLink()
.setDestination(R.id.notificationSampleFragment)
.setArguments(args)
.createPendingIntent()
val notificationManager =
context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.createNotificationChannel(
NotificationChannel(
"deeplink", "Deep Links", NotificationManager.IMPORTANCE_HIGH)
)
}
val builder = NotificationCompat.Builder(
context!!, "deeplink")
.setContentTitle(resources.getString(R.string.app_name))
.setContentText("Navigation 深層連接測試")
.setSmallIcon(R.mipmap.jetpack)
.setContentIntent(deep)
.setAutoCancel(true)
notificationManager.notify(0, builder.build())
複製代碼
咱們能夠建立一個DeepLink,帶上參數,經過Notification通知來測試這樣的效果,能夠直接跳到項目中的該頁面。
具體可查看SampleNotificationFragment。
官網上是這樣介紹它的:NavHostFragment provides an area within your layout for self-contained navigation to occur. 大體意思就是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) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
mNavController = new NavController(context);
mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
.......
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);
}
}
}
複製代碼
咱們看到在onCreate
生命週期中建立了一個NavController
,而且爲這個NavController
建立了一個_Navigator_
_添加了進去,_咱們跟蹤createFragmentNavigator
,發現它建立了一個FragmentNavigator
,這個類是作什麼的呢?它繼承了Navigator,查看註釋咱們知道它是爲每一個Navigation設置策略的,也就是說Fragment之間經過導航切換都是由它來操做的,下面會詳細介紹的,這裏先簡單看下。
接下來咱們看到爲NavController
設置了setGraph()
,也就是咱們xml裏面定義的navGraph
,導航佈局裏面的Fragment
及action
跳轉等信息。
還有就是onCreateView、onViewCreated等生命週期方法,基本就是加載佈局設置ID的方法了。
下面咱們跟到NavController.setGraph()中看下是怎樣將咱們設計的fragment添加進去的?
/** * 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:
boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) {
.....
.....
boolean popped = false;
for (Navigator navigator : popOperations) {
if (navigator.popBackStack()) {
mBackStack.removeLast();
popped = true;
} else {
// The pop did not complete successfully, so stop immediately
break;
}
}
return popped;
}
複製代碼
果然remove掉了以前全部的naviagtor。而這個mBackStack是何時添加的navigator的呢?查看源碼咱們發現:
private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = 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) {
// 若是NavGraph不在棧內,先拿到父類Navgarph
ArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>();
NavGraph parent = newDest.getParent();
while (parent != null) {
hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs));
parent = parent.getParent();
}
// 如今遍歷後堆棧並查看哪些導航圖已經在棧內
Iterator<NavBackStackEntry> iterator = mBackStack.iterator();
while (iterator.hasNext() && !hierarchy.isEmpty()) {
NavDestination destination = iterator.next().getDestination();
if (destination.equals(hierarchy.getFirst().getDestination())) {
//destination 若是已經在棧頂,不須要再add了
hierarchy.removeFirst();
}
}
// Add all of the remaining parent NavGraphs that aren't
// already on the back stack
mBackStack.addAll(hierarchy);
//添加新的 destination
NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest, finalArgs);
mBackStack.add(newBackStackEntry);
}
if (popped || newDest != null) {
dispatchOnDestinationChanged();
}
}
複製代碼
還記得這個方法嗎?咱們通常手動切換Fragment時能夠調用這個方法,最後就是跟蹤到這裏。
findNavController().navigate(R.id.bottomNavSampleActivity)
複製代碼
同時,切換目標Fragment到棧頂。咱們發現最後dispatchOnDestinationChanged()
這個方法,分發目標界面切換。有必要去跟一下,你可能會發現意想不到的東西:
/** * Dispatch changes to all OnDestinationChangedListeners. * <p> * If the back stack is empty, no events get dispatched. * * @return If changes were dispatched. */
@SuppressWarnings("WeakerAccess") /* synthetic access */
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()) {
NavBackStackEntry backStackEntry = mBackStack.peekLast();
for (OnDestinationChangedListener listener :
mOnDestinationChangedListeners) {
listener.onDestinationChanged(this, backStackEntry.getDestination(),
backStackEntry.getArguments());
}
return true;
}
return false;
}
複製代碼
這裏面分發了全部實現了OnDestinationChangedListener
接口的方法,繼續跟蹤,看看都哪些實現了這個接口呢?
只有一個類實現了AbstractAppBarOnDestinationChangedListener,看一下具體實現:
@Override
public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
DrawerLayout drawerLayout = mDrawerLayoutWeakReference != null
? mDrawerLayoutWeakReference.get()
: null;
if (mDrawerLayoutWeakReference != null && drawerLayout == null) {
controller.removeOnDestinationChangedListener(this);
return;
}
CharSequence label = destination.getLabel();
if (!TextUtils.isEmpty(label)) {
......
......
matcher.appendTail(title);
//設置title
setTitle(title);
}
boolean isTopLevelDestination = NavigationUI.matchDestinations(destination,
mTopLevelDestinations);
if (drawerLayout == null && isTopLevelDestination) {
//設置icon
setNavigationIcon(null, 0);
} else {
//設置返回箭頭狀態
setActionBarUpIndicator(drawerLayout != null && isTopLevelDestination);
}
}
複製代碼
原來如此,到這裏就應該清楚了,當咱們切換Fragment時,大概流程以下:
到這裏,基本的幾個核心類以及相關實現咱們基本瞭解了,下面咱們看一下基本的流程,首先咱們從入口進去,一點點跟進
咱們在最開始會初始化一個NavController:
@NonNull
public static NavController findNavController(@NonNull Activity activity, @IdRes int viewId) {
View view = ActivityCompat.requireViewById(activity, viewId);
NavController navController = findViewNavController(view);
.......
return navController;
}
@Nullable
private static NavController findViewNavController(@NonNull View view) {
while (view != null) {
NavController controller = getViewNavController(view);
.........
}
return null;
}
@SuppressWarnings("unchecked")
@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;
}
複製代碼
查看代碼能夠看到是經過一個tag值來找到的,那麼何時設置的呢?還記得3.1裏面介紹的NavHostFragment
的生命週期onViewCreated
麼?
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
.......
View rootView = view.getParent() != null ? (View) view.getParent() : view;
Navigation.setViewNavController(rootView, mNavController);
}
複製代碼
在視圖建立的時候調用了Naviagtion.setViewNavController()
。NavController初始化好了以後,接下來將它和NavigationView、ToolBar、BottomNavigationView、DrawerLayout進行綁定:
不論是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。這樣就將組件的狀態切換綁定起來了,當fragment切換時,上面提到的接口分發,去切換佈局按鈕等狀態。
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 DrawerLayout) {
((DrawerLayout) parent).closeDrawer(navigationView);
} 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()));
}
}
});
}
複製代碼
最後就是狀態切換了,當點擊menu菜單或者目標Fragment切換的時候,改變狀態。
遺留:還記得上面說的那個在設置menu菜單欄的item的ID要和navigation.xml裏fragment的ID相同麼?至於爲何要這麼作,咱們看上面的第一段代碼:跟蹤onNavDestinationSelected():
public static boolean onNavDestinationSelected(@NonNull MenuItem item, @NonNull NavController navController) {
.......
.......
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, @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;
......
......
//根據menu id查詢目標頁面
NavDestination node = findDestination(destId);
if (node == null) {
final String dest = NavDestination.getDisplayName(mContext, destId);
throw new IllegalArgumentException("navigation destination " + dest
+ (navAction != null
? " referenced from action " + NavDestination.getDisplayName(mContext, resId)
: "")
+ " is unknown to this NavController");
}
navigate(node, combinedArgs, navOptions, navigatorExtras);
}
複製代碼
NavDestination node = findDestination(destId)
經過Menu Item的ID查詢NavDestination:
**
@SuppressWarnings("WeakerAccess") /* synthetic access */
NavDestination findDestination(@IdRes int destinationId) {
.......
return currentGraph.findNode(destinationId);
}
@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中的每一個fragment的相關信息:
在初始化的時候經過addDestination()
放到數組mNodes中,而mId則就是咱們的MenuItem的ID,因此很清楚了吧。
navigate()
方法,在裏面dispatchOnDestinationChanged()吸引了個人注意力,經過查找,發現切換Fragment以後,經過該方法去改變佈局的狀態,也就是OnDestinationChangedListener接口。navigate()
方法後,改變狀態,整個流程就走通了。可能有一些不合理的地方,望你們見諒,可是這是我這次的一個基本流程。
咱們在Activity的佈局裏面設置了NavHostFragment,同時設置了navGraph佈局,通過上面的分析咱們知道NavHostFragment中新建了NavController,而且建立了用來管理Fragment事務及切換的FragmentNavigator,能夠簡單的把它理解成鏈接Fragment和NavController的一個橋樑,同時也提供了包含導航的容器佈局。
NavContorller是整個導航組件的核心,經過它來加載xml中fragment節點轉化成NavDestination,並保存在棧內,經過navigate()方法切換棧內NavDestination,以作到fragment的切換操做。同時當fragment切換後,下發OnDestinationChanged接口,來改變NavgationView、BottomNavgationView、Menu等相關UI操做。
經過NavgationUI類,爲各個View設置接口監聽,將View的UI狀態和NavController中的切換Fragment作了綁定。
到這裏整個Navgation組件的源碼分析就結束了,大概的流程已經很清晰了,固然沒有作到百分百,好比Deep Link部分,感興趣的能夠自行看一下,能夠按照這個思路去真的看一下源碼,看完以後你真的會對Navgation組件有更深的理解。固然你也能夠參考CodeLabs中的Demo以及文檔,也能夠看個人Jepack_Note的代碼,若有不對的地方,還望指出,諒解.