一文詳解Jetpack?Android新一代導航管理Navigation
前言
不知道小伙伴們是否注意到,用AS創(chuàng)建一個默認的新項目后,MainActivity已經(jīng)有了很大的不同,最大的區(qū)別就是新增加了兩個Fragment,同時我們注意到這兩個Fragment之間跳轉的時候并沒有使用之前FragmentTransaction這種形式,而是使用了NavController和NavHostFragment,這就是新一代導航管理————Navigation。
項目中依賴Navigation:
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
創(chuàng)建導航視圖
新建一個Android Resource File,類型選擇Navigation即可,輸入名稱后我們就創(chuàng)建了一個導航視圖。
在導航試圖中,我們可以通過添加activity/fragment等標簽手動添加頁面,也支持在Design頁面中通過界面添加,如下:

注意:這樣添加后手動修改一下label。如果我們將Navigation與ToolBar連接,會在標題欄這個label。
示例中添加了兩個頁面,添加后代碼如下:
<?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">
<fragment
android:id="@+id/FirstFragment"
android:name="com.xxx.xxx.FirstFragment"
android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_first">
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:name="com.xxx.xxx.SecondFragment"
android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_second">
</fragment>
</navigation>
除了添加Fragment和Activity,Google還提供了一個占位符placeholder,添加加完代碼如下:
<fragment android:id="@+id/placeholder" />
用于暫時占位以便后面可以替換為Fragment和Activity
添加完頁面后,我們還需要添加頁面之間的導航,可以手動添加action標簽,當然也可以通過拖拽來實現(xiàn),如下:

這樣我們就添加了一個從FirstFragment導航到SecondFragment的動作,我們再添加一個逆向的動作,最終的代碼如下:
<?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">
<fragment
android:id="@+id/FirstFragment"
android:name="com.xxx.xxx.FirstFragment"
android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_first">
<action
android:id="@+id/action_FirstFragment_to_SecondFragment"
app:destination="@id/SecondFragment" />
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:name="com.xxx.xxx.SecondFragment"
android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_second">
<action
android:id="@+id/action_SecondFragment_to_FirstFragment"
app:destination="@id/FirstFragment" />
</fragment>
</navigation>
注意占位符placeholder同樣支持添加導航。
這樣就實現(xiàn)了兩個頁面間的導航,最后還需要為這個navigation設置id和默認頁面startDestination,如下:
<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/nav_graph"
app:startDestination="@id/FirstFragment">
這樣導航視圖就創(chuàng)建完成了??梢钥吹紾oogle力圖通過可視化工具來簡化開發(fā)工作,這對我們開發(fā)者來說非常有用,可以省去大量編寫同質化代碼的時間。
添加NavHost
下一步我們需要向Activity中添加導航宿主,導航宿主是一個空頁面,必須實現(xiàn)NavHost接口,我們使用Navigation提供的默認NavHost————NavHostFragment即可。如下:
<fragment
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
在Activity的視圖中添加一個fragment標簽,android:name設置為實現(xiàn)類,即NavHostFragment;app:navGraph設置為剛才新建的導航視圖。
注意app:defaultNavHost="true",設置為true后表示將這個NavHostFragment設置為默認導航宿主,這樣就會攔截系統(tǒng)的返回按鈕事件。同一布局中如果有多個導航宿主(比如雙窗口)則必須制定一個為默認的導航宿主。
這時候我們運行應用,就可以發(fā)現(xiàn)Activity中已經(jīng)可以展示FirstFragment了。
導航
我們還需要為兩個fragment添加按鈕,是其點擊跳轉到另外一個頁面,代碼如下:
binding.buttonFirst.setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
示例中是FirstFragment中的一個按鈕,點擊時執(zhí)行了id為action_FirstFragment_to_SecondFragment的動作,這個是我們之前在導航視圖中配置好的,會導航到SecondFragment。
注意首先通過findNavController()來獲取一個NavController對象,然后調用它的navigate函數(shù)即可,當然這個函數(shù)有多種重載,比如可以傳遞參數(shù),如下:
public void navigate(@IdRes int resId, @Nullable Bundle args) {
這里不一一列舉了,大家自行查看源碼即可。
可以看到使用Navigation代碼精簡了很多,只需要一行代碼執(zhí)行一個函數(shù)即可。
findNavController
我們重點來看看findNavController(),它是一個擴展函數(shù),如下:
fun Fragment.findNavController(): NavController =
NavHostFragment.findNavController(this)
實際上是NavHostFragment的一個靜態(tài)函數(shù)findNavController:
@NonNull
public static NavController findNavController(@NonNull Fragment fragment) {
...
View view = fragment.getView();
if (view != null) {
return Navigation.findNavController(view);
}
// For DialogFragments, look at the dialog's decor view
Dialog dialog = fragment instanceof DialogFragment
? ((DialogFragment) fragment).getDialog()
: null;
if (dialog != null && dialog.getWindow() != null) {
return Navigation.findNavController(dialog.getWindow().getDecorView());
}
throw new IllegalStateException("Fragment " + fragment
+ " does not have a NavController set");
}
通過源碼可以看到最終是執(zhí)行了Navigation的findNavController函數(shù),它的代碼如下:
@NonNull
public static NavController findNavController(@NonNull View view) {
NavController navController = findViewNavController(view);
...
return navController;
}
這里是通過findViewNavController函數(shù)來獲取NavController的,它的代碼如下:
@Nullable
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來獲取NavController,如果沒有則向上層查找(父view)直到找到或到根結點。getViewNavController代碼如下:
@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;
}
看到這里獲取view中key為R.id.nav_controller_view_tag的tag,這個tag就是NavController,那么這個tag又從哪來的?
其實就是上面我們提到導航宿主————NavHostFragment,在他的onViewCreated中可以看到如下代碼:
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (!(view instanceof ViewGroup)) {
throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
}
Navigation.setViewNavController(view, mNavController);
// When added programmatically, we need to set the NavController on the parent - i.e.,
// the View that has the ID matching this NavHostFragment.
if (view.getParent() != null) {
mViewParent = (View) view.getParent();
if (mViewParent.getId() == getId()) {
Navigation.setViewNavController(mViewParent, mNavController);
}
}
}
這里的mNavController是在NavHostFragment的onCreate中創(chuàng)建出來的,是一個NavHostController對象,它繼承NavController,所以就是NavController。
可以看到onViewCreated中調用了Navigation的setViewNavController函數(shù),它的代碼如下:
public static void setViewNavController(@NonNull View view,
@Nullable NavController controller) {
view.setTag(R.id.nav_controller_view_tag, controller);
}
這樣就將NavController加入tag中了,通過findNavController()就可以得到這個NavController來執(zhí)行導航了。
注意在onViewCreated中不僅為Fragment的View添加了tag,同時還為其父View也添加了,這樣做的目的是在Activity中也可以獲取到NavController,這點下面就會遇到。
ToolBar
Google提供了Navigation與ToolBar連接的功能,代碼如下:
val navController = findNavController(R.id.nav_host_fragment_content_main) appBarConfiguration = AppBarConfiguration(navController.graph) setupActionBarWithNavController(navController, appBarConfiguration)
上面我們提到,如果Navigation與ToolBar連接,標題欄會自動顯示在導航視圖中設定好的label。
注意這里的findNavController是Activity的擴展函數(shù),它最終一樣會調用Navigation的對應函數(shù),所以與Fragment的流程是一樣的。而上面我們提到了,在NavHostFragment中給上層View也設置了tag,所以在這里才能獲取到NavController。
除了這個,我們還可以發(fā)現(xiàn)當在切換頁面的時候,標題欄的返回按鈕也會自動顯示和隱藏。當導航到第二個頁面SecondFragment,返回按鈕顯示;當回退到首頁時,返回按鈕隱藏。
但是此時返回按鈕點擊無效,因為我們還需要重寫一個函數(shù):
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
這樣當點擊標題欄的返回按鈕時,會執(zhí)行NavController的navigateUp函數(shù),就會退回到上一頁面。
總結
可以看出通過Google推出的這個Navigation,可以讓開發(fā)者更加優(yōu)雅管理導航,同時也簡化了這部分的開發(fā)工作,可視化功能可以讓開發(fā)者更直觀的進行管理。除此之外,Google還提供了Safe Args Gradle插件,該插件可以生成簡單的對象和構建器類,這些類支持在目的地之間進行類型安全的導航和參數(shù)傳遞。關于這個大家可以參考官方文檔developer.android.google.cn/guide/navig… 即可。
以上就是一文詳解Jetpack Android新一代導航管理Navigation的詳細內容,更多關于Jetpack Android導航管理Navigation的資料請關注腳本之家其它相關文章!
相關文章
android 解決ViewPager加載大量圖片內存溢出問題
本篇文章是介紹 android 解決ViewPager加載大量圖片內存溢出問題,并附有代碼實例,希望能幫到有需要的小伙伴2016-07-07
如何使用Flutter實現(xiàn)58同城中的加載動畫詳解
這篇文章主要給大家介紹了關于如何使用Flutter實現(xiàn)58同城中加載動畫詳?shù)南嚓P資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Flutter具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-10-10
Android PC端用ADB抓取指定應用日志實現(xiàn)步驟
這篇文章主要介紹了Android PC端用ADB抓取指定應用日志實現(xiàn)步驟,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04
Android中判斷是否聯(lián)網(wǎng)實現(xiàn)代碼
這篇文章主要介紹了Android中判斷是否聯(lián)網(wǎng)實現(xiàn)代碼,本文直接給出實現(xiàn)代碼,需要的朋友可以參考下2015-06-06

