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

注意:這樣添加后手動(dòng)修改一下label。如果我們將Navigation與ToolBar連接,會(huì)在標(biāo)題欄這個(gè)label。
示例中添加了兩個(gè)頁面,添加后代碼如下:
<?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還提供了一個(gè)占位符placeholder,添加加完代碼如下:
<fragment android:id="@+id/placeholder" />
用于暫時(shí)占位以便后面可以替換為Fragment和Activity
添加完頁面后,我們還需要添加頁面之間的導(dǎo)航,可以手動(dòng)添加action標(biāo)簽,當(dāng)然也可以通過拖拽來實(shí)現(xiàn),如下:

這樣我們就添加了一個(gè)從FirstFragment導(dǎo)航到SecondFragment的動(dòng)作,我們?cè)偬砑右粋€(gè)逆向的動(dòng)作,最終的代碼如下:
<?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同樣支持添加導(dǎo)航。
這樣就實(shí)現(xiàn)了兩個(gè)頁面間的導(dǎo)航,最后還需要為這個(gè)navigation設(shè)置id和默認(rèn)頁面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">
這樣導(dǎo)航視圖就創(chuàng)建完成了??梢钥吹紾oogle力圖通過可視化工具來簡(jiǎn)化開發(fā)工作,這對(duì)我們開發(fā)者來說非常有用,可以省去大量編寫同質(zhì)化代碼的時(shí)間。
添加NavHost
下一步我們需要向Activity中添加導(dǎo)航宿主,導(dǎo)航宿主是一個(gè)空頁面,必須實(shí)現(xiàn)NavHost接口,我們使用Navigation提供的默認(rèn)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的視圖中添加一個(gè)fragment標(biāo)簽,android:name設(shè)置為實(shí)現(xiàn)類,即NavHostFragment;app:navGraph設(shè)置為剛才新建的導(dǎo)航視圖。
注意app:defaultNavHost="true",設(shè)置為true后表示將這個(gè)NavHostFragment設(shè)置為默認(rèn)導(dǎo)航宿主,這樣就會(huì)攔截系統(tǒng)的返回按鈕事件。同一布局中如果有多個(gè)導(dǎo)航宿主(比如雙窗口)則必須制定一個(gè)為默認(rèn)的導(dǎo)航宿主。
這時(shí)候我們運(yùn)行應(yīng)用,就可以發(fā)現(xiàn)Activity中已經(jīng)可以展示FirstFragment了。
導(dǎo)航
我們還需要為兩個(gè)fragment添加按鈕,是其點(diǎn)擊跳轉(zhuǎn)到另外一個(gè)頁面,代碼如下:
binding.buttonFirst.setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
示例中是FirstFragment中的一個(gè)按鈕,點(diǎn)擊時(shí)執(zhí)行了id為action_FirstFragment_to_SecondFragment的動(dòng)作,這個(gè)是我們之前在導(dǎo)航視圖中配置好的,會(huì)導(dǎo)航到SecondFragment。
注意首先通過findNavController()來獲取一個(gè)NavController對(duì)象,然后調(diào)用它的navigate函數(shù)即可,當(dāng)然這個(gè)函數(shù)有多種重載,比如可以傳遞參數(shù),如下:
public void navigate(@IdRes int resId, @Nullable Bundle args) {
這里不一一列舉了,大家自行查看源碼即可。
可以看到使用Navigation代碼精簡(jiǎn)了很多,只需要一行代碼執(zhí)行一個(gè)函數(shù)即可。
findNavController
我們重點(diǎn)來看看findNavController(),它是一個(gè)擴(kuò)展函數(shù),如下:
fun Fragment.findNavController(): NavController =
NavHostFragment.findNavController(this)
實(shí)際上是NavHostFragment的一個(gè)靜態(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)直到找到或到根結(jié)點(diǎn)。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,這個(gè)tag就是NavController,那么這個(gè)tag又從哪來的?
其實(shí)就是上面我們提到導(dǎo)航宿主————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)建出來的,是一個(gè)NavHostController對(duì)象,它繼承NavController,所以就是NavController。
可以看到onViewCreated中調(diào)用了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()就可以得到這個(gè)NavController來執(zhí)行導(dǎo)航了。
注意在onViewCreated中不僅為Fragment的View添加了tag,同時(shí)還為其父View也添加了,這樣做的目的是在Activity中也可以獲取到NavController,這點(diǎn)下面就會(huì)遇到。
ToolBar
Google提供了Navigation與ToolBar連接的功能,代碼如下:
val navController = findNavController(R.id.nav_host_fragment_content_main) appBarConfiguration = AppBarConfiguration(navController.graph) setupActionBarWithNavController(navController, appBarConfiguration)
上面我們提到,如果Navigation與ToolBar連接,標(biāo)題欄會(huì)自動(dòng)顯示在導(dǎo)航視圖中設(shè)定好的label。
注意這里的findNavController是Activity的擴(kuò)展函數(shù),它最終一樣會(huì)調(diào)用Navigation的對(duì)應(yīng)函數(shù),所以與Fragment的流程是一樣的。而上面我們提到了,在NavHostFragment中給上層View也設(shè)置了tag,所以在這里才能獲取到NavController。
除了這個(gè),我們還可以發(fā)現(xiàn)當(dāng)在切換頁面的時(shí)候,標(biāo)題欄的返回按鈕也會(huì)自動(dòng)顯示和隱藏。當(dāng)導(dǎo)航到第二個(gè)頁面SecondFragment,返回按鈕顯示;當(dāng)回退到首頁時(shí),返回按鈕隱藏。
但是此時(shí)返回按鈕點(diǎn)擊無效,因?yàn)槲覀冞€需要重寫一個(gè)函數(shù):
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
這樣當(dāng)點(diǎn)擊標(biāo)題欄的返回按鈕時(shí),會(huì)執(zhí)行NavController的navigateUp函數(shù),就會(huì)退回到上一頁面。
總結(jié)
可以看出通過Google推出的這個(gè)Navigation,可以讓開發(fā)者更加優(yōu)雅管理導(dǎo)航,同時(shí)也簡(jiǎn)化了這部分的開發(fā)工作,可視化功能可以讓開發(fā)者更直觀的進(jìn)行管理。除此之外,Google還提供了Safe Args Gradle插件,該插件可以生成簡(jiǎn)單的對(duì)象和構(gòu)建器類,這些類支持在目的地之間進(jìn)行類型安全的導(dǎo)航和參數(shù)傳遞。關(guān)于這個(gè)大家可以參考官方文檔developer.android.google.cn/guide/navig… 即可。
以上就是一文詳解Jetpack Android新一代導(dǎo)航管理Navigation的詳細(xì)內(nèi)容,更多關(guān)于Jetpack Android導(dǎo)航管理Navigation的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Flutter通過Container實(shí)現(xiàn)時(shí)間軸效果
時(shí)間軸是前端UI經(jīng)常用到的效果,本文講解下Flutter如何通過Container實(shí)現(xiàn),感興趣的朋友可以了解下2021-05-05
Android開源項(xiàng)目PullToRefresh下拉刷新功能詳解2
這篇文章主要為大家進(jìn)一步的介紹了Android開源項(xiàng)目PullToRefresh下拉刷新功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android EditText長(zhǎng)按菜單中分享功能的隱藏方法
Android EditText控件是經(jīng)常使用的控件,下面這篇文章主要給大家介紹了關(guān)于Android中EditText長(zhǎng)按菜單中分享功能的隱藏方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02
Android動(dòng)畫 實(shí)現(xiàn)開關(guān)按鈕動(dòng)畫(屬性動(dòng)畫之平移動(dòng)畫)實(shí)例代碼
這篇文章主要介紹了Android動(dòng)畫 實(shí)現(xiàn)開關(guān)按鈕動(dòng)畫(屬性動(dòng)畫之平移動(dòng)畫)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-11-11
輕松實(shí)現(xiàn)Android3D效果通俗易懂
前幾天有粉絲要求計(jì)蒙寫一個(gè)3d效果的簡(jiǎn)單教程,其實(shí)這個(gè)在Android官方demo中是有的,可能對(duì)于新手而言看不太明白,于是根據(jù)本人自己的理解來寫一個(gè)教程,并改成粉絲要求的樣子2021-08-08
android Bitmap圓角與倒影的具體實(shí)現(xiàn)代碼
android Bitmap圓角與倒影的具體實(shí)現(xiàn)代碼,需要的朋友可以參考一下2013-06-06
利用Jetpack Compose繪制可愛的天氣動(dòng)畫
Jetpack Compose是用于構(gòu)建原生Android UI的現(xiàn)代工具包。Jetpack Compose使用更少的代碼,強(qiáng)大的工具和直觀的Kotlin API,簡(jiǎn)化并加速了Android上的UI開發(fā)。本文將利用Jetpack Compose繪制可愛的天氣動(dòng)畫,感興趣的可以了解一下2022-01-01

