一、前言
在日常開發中,越來越多的會使用到一個activity嵌套多個fragment的場景,典型的例子就是app的首頁,一般都會由一個activity+多個Fragment組成的底部導航界面,那對于Fragment的顯示、隱藏等我們通常都是通過FragmentManager進行管理,但這種方式很容易造成代碼臃腫,難以維護,
而通過Jetpack的導航組件——Navigation,就可以很方便的管理各fragment之間的切換,讓開發變得更簡單,官方檔案
Navigation主要由三部分組成:
- Navigation graph:一個包含所有導航相關資訊的 XML 資源
- NavHostFragment:一種特殊的Fragment,用于承載導航內容的容器
- NavController:管理應用導航的物件,實作Fragment之間的跳轉等操作
二、基本使用
2.1 添加依賴
dependencies {
def nav_version = "2.3.1"
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
2.2 創建導航圖
- 在“Project”視窗中,右鍵點擊 res 目錄,然后依次選擇 New > Android Resource File,此時系統會顯示 New Resource File 對話框,
- 在 File name 欄位中輸入名稱,例如“nav_graph”,
- 從 Resource type 下拉串列中選擇 Navigation,然后點擊 OK,

2.3 Navigation graph
新建好的nav_graph.xml切換到design模式下,在 Navigation Editor 中,點擊 New Destination 圖示,然后點擊 Create new destination,即可快速創建新的Fragment,這里分別新建了Fragme0ntA、FragmentB、FragmentC三個fragment

可通過手動配置頁面之間的跳轉關系,點擊某個頁面,右邊會出現一個小圓點,拖曳小圓點指向跳轉的頁面,這里設定跳轉的關系為FragmentA -> FragmentB -> FragmentC,

切換到Code欄,可以看到生成了如下代碼
<?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/nav_graph"
app:startDestination="@id/fragmentA">
<fragment
android:id="@+id/fragmentA"
android:name="com.example.testnavigation.FragmentA"
android:label="fragment_a"
tools:layout="@layout/fragment_a" >
<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB" />
</fragment>
<fragment
android:id="@+id/fragmentB"
android:name="com.example.testnavigation.FragmentB"
android:label="fragment_b"
tools:layout="@layout/fragment_b" >
<action
android:id="@+id/action_fragmentB_to_fragmentC"
app:destination="@id/fragmentC" />
</fragment>
<fragment
android:id="@+id/fragmentC"
android:name="com.example.testnavigation.FragmentC"
android:label="fragment_c"
tools:layout="@layout/fragment_c" />
</navigation>
- navigation是根標簽,通過
startDestination配置默認啟動的第一個頁面,這里配置的是FragmentA - fragment標簽代表一個fragment
- action標簽定義了頁面跳轉的行為,相當于上圖中的每條線,
destination定義跳轉的目標頁,還可以定義跳轉時的影片等等
2.4 NavHostFragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
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" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:name指定NavHostFragmentapp:navGraph指定導航視圖,即建好的nav_graph.xmlapp:defaultNavHost=true意思是可以攔截系統的回傳鍵,可以理解為默認給fragment實作了回傳鍵的功能,這樣在fragment的跳轉程序中,當我們按回傳鍵時,就可以使得fragment跟activity一樣可以回到上一個頁面了
現在我們運行程式,就可以正常跑起來了,并且看到了FragmentA展示的頁面,這是因為MainActivity的布局檔案中配置了NavHostFragment,并且給NavHostFragment指定了導航視圖,而導航視圖中通過startDestination指定了默認展示FragmentA,
2.5 NavController
NavController用來管理fragment之間的跳轉
每個 NavHost 均有自己的相應 NavController,您可以使用以下方法之一檢索 NavController:
Kotlin:
Fragment.findNavController()View.findNavController()Activity.findNavController(viewId: Int)
Java:
NavHostFragment.findNavController(Fragment)Navigation.findNavController(Activity, @IdRes int viewId)Navigation.findNavController(View)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val navController = findNavController()
navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
}
2.6 使用 Safe Args 確保型別安全
fragment之間的跳轉上面已經可以實作了,但是Google建議使用 Safe Args Gradle 插件實作,該插件可以生成簡單的物件和構建器類,這些類支持在目的地之間進行型別安全的導航和引數傳遞,
如需將 Safe Args 添加到您的專案,請在頂層 build.gradle 檔案中包含以下 classpath:
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.1"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
請將以下行添加到應用或模塊的 build.gradle 檔案中:
//適用于 Java 模塊或 Java 和 Kotlin 混合模塊的 Java 語言代碼
// apply plugin: "androidx.navigation.safeargs"
//僅 Kotlin 模塊的 Kotlin 語言代碼
apply plugin: "androidx.navigation.safeargs.kotlin"
啟用 Safe Args 后,該插件會生成代碼,其中包含您定義的每個操作的類和方法,生成的類的名稱由源目的地類的名稱(即Fragment類名稱)和“Directions”一詞組成,
例如,上面的導航圖中,源目的地 fragmentA跳轉到接收目的地 fragmentB,Safe Args 會生成一個 fragmentADirections類,其中只包含一個 actionFragmentAToFragmentB() 方法(該方法會回傳 NavDirections 物件),然后,您可以將回傳的 NavDirections 物件直接傳遞到 navigate(),如以下示例所示:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val action = fragmentADirections.actionFragmentAToFragmentB()
findNavController().navigate(action)
}
}
三、更多操作
3.1 嵌套導航圖
您可以將一系列目的地歸入父級導航圖(稱為“根圖”)內的一個嵌套圖中,嵌套圖對于整理和重復使用應用界面的各個部分(例如獨立的登錄流程)非常有用,
如下圖顯示了一個簡單的轉帳應用的導航圖,從左側的起始目的地中,該圖分出兩個流程:一個位于頂部,用于匯款;另一個位于底部,用于查看帳號余額,

如需將目的地歸入一個嵌套圖中,請執行以下操作:
- 在 Navigation Editor 中,按住 Shift 鍵,然后點擊您想要添加到嵌套圖中的目的地,
- 右鍵點擊以打開背景關系選單,然后依次選擇 Move to Nested Graph > New Graph,目的地包含在嵌套圖中,

- 雙擊嵌套圖以顯示其目的地,
- 點擊 Text 標簽頁,以切換到 XML 視圖,一個嵌套導航圖已添加到該圖中
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.cashdog.cashdog.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main" >
<action
android:id="@+id/action_mainFragment_to_sendMoneyGraph"
app:destination="@id/sendMoneyGraph" />
<action
android:id="@+id/action_mainFragment_to_viewBalanceFragment"
app:destination="@id/viewBalanceFragment" />
</fragment>
<fragment
android:id="@+id/viewBalanceFragment"
android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
android:label="fragment_view_balance"
tools:layout="@layout/fragment_view_balance" />
<navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
<fragment
android:id="@+id/chooseRecipient"
android:name="com.example.cashdog.cashdog.ChooseRecipient"
android:label="fragment_choose_recipient"
tools:layout="@layout/fragment_choose_recipient">
<action
android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
app:destination="@id/chooseAmountFragment" />
</fragment>
<fragment
android:id="@+id/chooseAmountFragment"
android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
android:label="fragment_choose_amount"
tools:layout="@layout/fragment_choose_amount" />
</navigation>
</navigation>
- 跳轉方法相同
findNavController().navigate(R.id.action_mainFragment_to_sendMoneyGraph)
//findNavController().navigate(mainFragmentDirections.actionMainFragmentToSendMoneyGraph)
3.2 全域操作
您可以使用全域操作來創建可由多個目的地共用的通用操作,例如,您可能想要不同目的地中的多個按鈕導航到同一應用主螢屏,
如需創建全域操作,請執行以下操作:
- 在 Graph Editor 中,點擊一個目的地,使其突出顯示,
- 右鍵點擊該目的地,以顯示背景關系選單,
- 依次選擇 Add Action > Global,此時系統會在該目的地左側顯示一個箭頭,
- 點擊 Text 標簽頁,以轉到 XML 文本視圖,全域操作的 XML 文本大致如下所示:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_nav"
app:startDestination="@id/mainFragment">
...
<action android:id="@+id/action_global_mainFragment"
app:destination="@id/mainFragment"/>
</navigation>
- 跳轉方法相同
findNavController().navigate(R.id.action_global_mainFragment)
將 Safe Args 用于全域操作時,您必須為根 元素提供一個 android:id 值,如以下示例中所示:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nav_graph"
app:startDestination="@id/mainFragment">
...
</navigation>
Navigation 會根據 android:id 值為 < navigation > 元素生成一個 Directions 類,
findNavController().navigate(NavigationDirections.actionGlobalMainFragment)
3.3 添加影片
在 Attributes 面板的 Animations 部分中,點擊要添加的影片旁邊的下拉箭頭,您可以從以下型別中進行選擇:

enterAnim: 跳轉時的目標頁面影片exitAnim: 跳轉時的原頁面影片popEnterAnim: 回退時的目標頁面影片popExitAnim:回退時的原頁面影片
配置影片后會發現action多了四個影片相關的屬性
<fragment
android:id="@+id/fragmentA"
android:name="com.example.testnavigation.FragmentA"
android:label="fragment_a"
tools:layout="@layout/fragment_a" >
<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB"
app:enterAnim="@anim/enter_in"
app:exitAnim="@anim/enter_out"
app:popEnterAnim="@anim/exit_in"
app:popExitAnim="@anim/exit_out" />
</fragment>
3.4 傳遞引數
通常情況下,強烈建議您僅在目的地之間傳遞最少量的資料,因為在 Android 上用于保存所有狀態的總空間是有限的,如果您需要傳遞大量資料,不妨考慮使用 ViewModel
3.4.1 使用 Bundle 物件在目的地之間傳遞引數
傳遞資料,創建 Bundle 物件并使用 navigate() 將它傳遞給目的地
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val bundle = Bundle()
bundle.putString("key", "from fragmentA")
findNavController().navigate(R.id.action_fragmentA_to_fragmentB, bundle))
}
}
接收資料,getArguments() 方法來檢索 Bundle 并使用其內容:
val keyStr = arguments?.getString("key")
3.4.2 使用 Safe Args 傳遞安全的資料
如需在目的地之間傳遞資料,首先請按照以下步驟將引數添加到接收它的目的地來定義引數:
- 在 Navigation Editor 中,點擊接收引數的目的地,
- 在 Attributes 面板中,點擊 Add (+),
- 在顯示的 Add Argument Link 視窗中,輸入引數名稱、引數型別、引數是否可為 null,以及默認值(如果需要),
- 點擊 Add,請注意,該引數現在會顯示在 Attributes 面板的 Arguments 串列中,

點擊 Text 標簽頁以切換到 XML 視圖,就會發現您的引數已添加到接收該引數的目的地,相關示例如下所示:
<fragment android:id="@+id/fragmentB" >
<argument
android:name="key"
app:argType="String"
android:defaultValue="test" />
</fragment>
Navigation 庫支持以下引數型別:
| 型別 | app:argType 語法 | 是否支持默認值? | 是否支持 null 值? |
|---|---|---|---|
| 整數 | app:argType=“integer” | 是 | 否 |
| 浮點數 | app:argType=“float” | 是 | 否 |
| 長整數 | app:argType=“long” | 是 - 默認值必須始終以“L”后綴結尾(例如“123L”), | 否 |
| 布林值 | app:argType=“boolean” | 是 -“true”或“false” | 否 |
| 字串 | app:argType=“string” | 是 | 是 |
| 資源參考 | app:argType=“reference” | 是 - 默認值必須為“@resourceType/resourceName”格式(例如,“@style/myCustomStyle”)或“0” | 否 |
| 自定義 Parcelable | app:argType="< type>",其中 < type> 是 Parcelable 的完全限定類名稱 | 支持默認值“@null”,不支持其他默認值, | 是 |
| 自定義 Serializable | app:argType="< type>",其中 < type> 是 Serializable 的完全限定類名稱 | 支持默認值“@null”,不支持其他默認值, | 是 |
| 自定義 Enum | app:argType="< type>",其中 < type> 是 Enum 的完全限定名稱 | 是 - 默認值必須與非限定名稱匹配(例如,“SUCCESS”匹配 MyEnum.SUCCESS), | 否 |
- 如果引數型別支持 null 值,您可以使用
android:defaultValue="@null"宣告默認值 null, - 如果您選擇其中一種自定義型別,則系統會顯示 Select Class 對話框,提示您選擇與該型別對應的類,您可以通過 Project 標簽頁從當前專案中選擇類,
- 您可以選擇 < inferred type>,讓 Navigation 庫根據提供的值來確定型別,
- 您可以選中 Array,以指明引數應該是所選 Type 值的陣列,請注意,不支持列舉陣列和資源參考陣列,不論基礎型別的 null 性如何,陣列始終可為 null,陣列僅支持一個默認值,即“@null”,陣列不支持其他任何默認值,
傳遞資料
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val action = fragmentADirections.actionFragmentAToFragmentB("from fragmentA")
findNavController().navigate(action)
}
}
接收資料
Safe Args為接收目的地創建一個類,該類的名稱是在目的地的名稱后面加上“Args”,例如,如果目的地 Fragment 的名稱為 fragmentB ,,則生成的類的名稱為 fragmentBArgs,可以使用該類的 fromBundle() 方法檢索引數,
val args: fragmentBArgs by navArgs()
val keyStr = args.key
3.5 堆疊管理
點擊action即連接線,右側面板中還可以看到popUpTo、popUpToInclusive、launchSingleTop

launchSingleTop:如果堆疊中已經包含了指定要跳轉的界面,那么只會保留一個,不指定則堆疊中會出現兩個界面相同的Fragment資料,可以理解為類似activity的 singleTop,即堆疊頂復用模式popUpTo(tag):表示跳轉到某個tag,并將tag之上的元素出堆疊,popUpToInclusive:為true表示會彈出tag,false則不會
例子:FragmentA -> FragmentB -> FragmentC -> FragmentA
設定FragmentC -> FragmentA 的action為popUpTo=FragmentA ,popUpToInclusive=false,那么堆疊內元素變化為
最后會發現需要按兩次回傳鍵才會回退到桌面
設定popUpToInclusive=true時,堆疊內元素變化為

此時只需要按一次回傳鍵就回退到桌面了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/229886.html
標籤:其他
上一篇:react 中 Rap2的使用
