導航組件 官方文檔:https://developer.android.google.cn/guide/navigation?hl=zh_cn
一、設置環境
在應用的build.gradle文件中添加以下依賴
dependencies {
def nav_version = "2.3.0-alpha01"
// 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"
// Dynamic Feature Module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}
注意:如果您要在 Android Studio 中使用 Navigation 組件,則必須使用 Android Studio 3.3 或更高版本。
二、使用
2.1 創建導航圖
導航發生在應用中的各個目的地(即您的應用中用戶可以導航到的任意位置)之間。這些目的地是通過操作連接的。
導航圖是一種資源文件,其中包含您的所有目的地和操作。該圖表會顯示應用的所有導航路徑。
- “目的地”是指應用中的不同內容區域。
- “操作”是指目的地之間的邏輯連接,表示用戶可以採取的路徑。
要向項目添加導航圖,請執行以下操作:
- 在“Project”窗口中,右鍵點擊 res 目錄,然後依次選擇 New > Android Resource File。此時系統會顯示 New Resource File 對話框。
- 在 File name 字段中輸入名稱,例如“login_navigation”。
- 從 Resource type 下拉列表中選擇 Navigation,然後點擊 OK。
當您添加首個導航圖時,Android Studio 會在 res
目錄內創建一個 navigation
資源目錄。該目錄包含您的導航圖資源文件(例如 nav_graph.xml
)。
2.2 Navigation Editor
添加圖表後,Android Studio 會在 Navigation Editor 中打開該圖表。在 Navigation Editor 中,您可以直觀地修改導航圖,或直接修改底層 XML。
- Destinations panel:列出了導航宿主和目前位於 Graph Editor 中的所有目的地。
- Graph Editor:包含導航圖的視覺表示形式。您可以在 Design 視圖和 Text 視圖中的底層 XML 表示形式之間切換。
- Attributes:顯示導航圖中當前所選項的屬性。
點擊split 查看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"
android:id="@+id/login_navigation">
</navigation>
<navigation>
元素是導航圖的根元素。當您向圖表添加目的地和連接操作時,可以看到相應的 <destination>
和 <action>
元素在此處顯示爲子元素。如果您有嵌套圖表,它們將顯示爲子 <navigation>
元素。
2.3 向Activity添加NavHost
導航宿主是 Navigation 組件的核心部分之一。導航宿主是一個空容器,用戶在您的應用中導航時,目的地會在該容器中交換進出。
導航宿主必須派生於 NavHost。Navigation 組件的默認 NavHost 實現 (NavHostFragment) 負責處理 Fragment 目的地的交換。
注意:Navigation 組件旨在用於具有一個主 Activity 和多個 Fragment 目的地的應用。主 Activity 與導航圖相關聯,且包含一個負責根據需要交換目的地的 NavHostFragment
。在具有多個 Activity 目的地的應用中,每個 Activity 均擁有其自己的導航圖。
通過 XML 添加 NavHostFragment
以下 XML 示例顯示了作爲應用主 Activity 一部分的 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=".ui.LoginActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/login_navigation"></fragment>
</androidx.constraintlayout.widget.ConstraintLayout>
請注意以下幾點:
- android:name :包含 NavHost 實現的類名稱,這是一個用於放置管理 destination 的空視圖。
- app:navGraph :將 NavHostFragment 與導航圖相關聯。導航圖會在此 NavHostFragment 中指定用戶可以導航到的所有目的地。
- app:defaultNavHost="true" 屬性確保您的 NavHostFragment 會攔截系統返回按鈕。請注意,只能有一個默認 NavHost。如果同一佈局(例如,雙窗格佈局)中有多個主機,請務必僅指定一個默認 NavHost。
2.4 嚮導航圖添加destination
您可以從現有的 Fragment 或 Activity 創建目的地。您還可以使用 Navigation Editor 創建新目的地,或創建佔位符以便稍後替換爲 Fragment 或 Activity。
在本示例中,我們來創建一個新目的地。要使用 Navigation Editor 添加新目的地,請執行以下操作:
- 在 Navigation Editor 中,點擊 New Destination 圖標 ,然後點擊 Create new destination(也可以從下拉列表裏把已經創建的fragment直接添加爲目的地)。
- 在隨即顯示的 New Android Component 對話框中,創建您的 Fragment。如需詳細瞭解 Fragment,請參閱 Fragment 文檔。
當您返回到 Navigation Editor 中時,會發現 Android Studio 已將此目的地添加到圖表中。
2.5 destination詳解
點擊一個目的地以將其選中,並注意 Attributes 面板中顯示的以下屬性:
- Type 字段指示在您的源代碼中,該目的地是作爲 Fragment、Activity 還是其他自定義類實現的。
- Label 字段包含該目的地的 XML 佈局文件的名稱。
- ID 字段包含該目的地的 ID,它用於在代碼中引用該目的地。
- Class 下拉列表顯示與該目的地相關聯的類的名稱。您可以點擊此下拉列表,將相關聯的類更改爲其他destination類型。
點擊 Text 標籤頁可查看導航圖的 XML 視圖。XML 中同樣包含該目的地的 id、name、label 和 layout 屬性,如下所示:
<?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/login_navigation"
app:startDestination="@id/welcomeFragment">
<fragment
android:id="@+id/welcomeFragment"
android:name="com.gozap.jetpack.ui.fragment.WelcomeFragment"
tools:layout="@layout/fragment_welcome"
android:label="WelcomeFragment" />
<fragment
android:id="@+id/loginFragment"
android:name="com.gozap.jetpack.ui.fragment.LoginFragment"
tools:layout="@layout/fragment_login"
android:label="LoginFragment" />
<fragment
android:id="@+id/registerFragment"
android:name="com.gozap.jetpack.ui.fragment.RegisterFragment"
tools:layout="@layout/fragment_register"
android:label="RegisterFragment" />
</navigation>
2.6 將某個屏幕指定位startDestination
起始目的地是用戶打開您的應用時看到的第一個屏幕,也是用戶退出您的應用時看到的最後一個屏幕。Navigation Editor 使用房子圖標 來表示起始目的地。
所有目的地就緒後,您便可以選擇startDestination,實現方法如下:
1、<navigation ... app:startDestination="@id/welcomeFragment" > </navigation>
2、在 Design 標籤頁中,點擊相應destination,使其突出顯示,再點擊導航的圖標。
2.7 連接destination
操作是指目的地之間的邏輯連接。操作在導航圖中以箭頭表示。操作通常會將一個destination連接到另一個destination,不過您也可以創建全局操作,此類操作可讓您從應用中的任意位置轉到特定destination。
藉助操作,您可以表示用戶在您的應用中導航時可以採取的不同路徑。請注意,要實際導航到各個目的地,您仍然需要編寫代碼來執行導航操作。如需瞭解詳情,請參閱本主題後面的導航到目的地部分。
您可以使用 Navigation Editor 將兩個目的地連接起來,具體操作步驟如下:
- 在 Design 標籤頁中,將鼠標懸停在您希望用戶從中導航出來的目的地的右側。該目的地右側上方會顯示一個圓圈,如圖 4 所示。
- 點擊您希望用戶導航到的目的地,並將光標拖動到該目的地的上方,然後鬆開。這兩個目的地之間生成的線條表示操作,如圖 5 所示。
<?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/login_navigation"
app:startDestination="@id/welcomeFragment">
<fragment
android:id="@+id/welcomeFragment"
android:name="com.gozap.jetpack.ui.fragment.WelcomeFragment"
android:label="WelcomeFragment"
tools:layout="@layout/fragment_welcome">
<action
android:id="@+id/action_welcomeFragment_to_loginFragment"
app:destination="@id/loginFragment" />
<action
android:id="@+id/action_welcomeFragment_to_registerFragment"
app:destination="@id/registerFragment"
app:enterAnim="@anim/common_slide_in_right"
app:exitAnim="@anim/common_slide_out_left"
app:popEnterAnim="@anim/common_slide_in_left"
app:popExitAnim="@anim/common_slide_out_right" />
</fragment>
<fragment
android:id="@+id/loginFragment"
android:name="com.gozap.jetpack.ui.fragment.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login" />
<fragment
android:id="@+id/registerFragment"
android:name="com.gozap.jetpack.ui.fragment.RegisterFragment"
android:label="RegisterFragment"
tools:layout="@layout/fragment_register" />
</navigation>
在導航圖中,操作由 <action>
元素表示。操作至少應包含自己的 ID 和用戶應轉到的目的地的 ID。
三、 跳轉與數據傳遞
3.1 導航到目的地
官方文檔:https://developer.android.google.cn/guide/navigation/navigation-navigate?hl=zh_cn
導航到目的地是使用 NavController
完成的,後者是一個在 NavHost
中管理應用導航的對象。每個 NavHost
均有自己的相應 NavController
。您可以使用以下方法之一檢索 NavController
:
Kotlin:
Java:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)
3.1.1 使用 Safe Args 確保類型安全
要在目的地之間導航,建議使用 Safe Args Gradle 插件。該插件可以生成簡單的對象和構建器類,這些類支持在目的地之間進行類型安全的導航和參數傳遞。
頂級build.gradle文件添加以下
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.0-alpha01"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
您還必須應用build.gradle 添加以下兩個可用插件之一。
java:
apply plugin: "androidx.navigation.safeargs"
kotlin
apply plugin: "androidx.navigation.safeargs.kotlin"
根據遷移到 AndroidX 文檔,您的 gradle.properties
文件 中必須具有 android.useAndroidX=true
。
啓用 Safe Args 後,生成的代碼會爲每個操作包含以下類型安全的類和方法,以及每個發送和接收目的地。
-
爲生成操作的每一個目的地創建一個類。該類的名稱是在源目的地的名稱後面加上“Directions”。例如,如果源目的地是名爲
WelcomeFragment
的 Fragment,則生成的類的名稱爲WelcomeFragmentDirections
。該類會爲源目的地中定義的每個操作提供一個方法。
-
對於用於傳遞參數的每個操作,都會創建一個 inner 類,該類的名稱根據操作的名稱確定。例如,如果操作名稱爲
confirmationAction,
,則類名稱爲ConfirmationAction
。如果您的操作包含不帶defaultValue
的參數,則您可以使用關聯的 action 類來設置參數值。 -
爲接收目的地創建一個類。該類的名稱是在目的地的名稱後面加上“Args”。例如,如果目的地 Fragment 的名稱爲
RegisterFragment,
,則生成的類的名稱爲RegisterFragmentArgs
。可以使用該類的fromBundle()
方法檢索參數。
啓用 Safe Args 後,該插件會生成代碼,其中包含您定義的每個操作的類和方法。對於每個操作,Safe Args 還會爲每個源目的地(生成相應操作的目的地)生成一個類。生成的類的名稱爲 "源目的地類的名稱+Directions” 組成。例如,如果目的地的名稱爲 WelcomeFragment
,則生成的類的名稱爲 WelcomeFragmentDirections
。生成的類爲源目的地中定義的每個操作提供了一個靜態方法。該方法會將任何定義的操作參數作爲參數,並返回可傳遞到 navigate()
的 NavDirections
對象。
例如,假設我們的導航圖包含一個操作,該操作將源目的地 WelcomeFragment
和接收目的地 LoginFragment
連接起來。
Safe Args 會生成一個 WelcomeFragmentDirections
類,其中只包含一個 action
WelcomeFragmentDirectionsToLoginFragment()
方法(該方法會返回 NavDirections
對象)。然後,您可以將返回的 NavDirections
對象直接傳遞到 navigate()
,如以下示例所示:
btn_login.setOnClickListener {
val action = WelcomeFragmentDirections.actionWelcomeFragmentToLoginFragment()
it.findNavController().navigate(action)
}
3.1.2 使用Id導航
navigate(int)
接受action或目的地的資源 ID 作爲參數。以下代碼段展示瞭如何導航到 RegisterFragment
:
btn_register.setOnClickListener {
//action id
it.findNavController().navigate(R.id.action_welcomeFragment_to_registerFragment)
// fragment id
it.findNavController().navigate(R.id.registerFragment)
}
注意:在使用 ID 進行導航時,我們強烈建議您儘可能使用action。action會在導航圖中提供更多信息,從而直觀顯示目的地之間如何相互連接。通過創建action,您可以將資源 ID 替換爲 Safe Args 生成的操作,從而進一步提高編譯時安全性。通過使用action,您還可以在目的地之間添加動畫過渡效果。如需瞭解詳情,請參閱在目的地之間添加動畫過渡效果。
對於按鈕,您還可以使用 Navigation
類的 createNavigateOnClickListener()
便捷方法導航到目的地,如下例所示:
(----測試未生效 ,還未找到原因----)
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null))
3.2 爲action提供導航選項
在導航圖中定義操作時,Navigation 會生成相應的 NavAction
類,其中包含爲該操作定義的配置,包括如下內容:
- Destination:The resource ID of the target destination.。
- Default arguments:
android.os.Bundle
,包含target destination的默認值(如有提供)。 - Navigation options:表示爲
NavOptions
。此類包含從 target destination往返的所有特殊配置,包括動畫資源配置、彈出行爲以及是否應在單一頂級模式下啓動目的地。
3.2.1 NavOptions
login_navigation.xml
<fragment
android:id="@+id/welcomeFragment"
android:name="com.gozap.jetpack.ui.fragment.WelcomeFragment"
android:label="WelcomeFragment"
tools:layout="@layout/fragment_welcome">
<action
android:id="@+id/action_welcomeFragment_to_loginFragment"
app:destination="@id/loginFragment" />
<!--設置動畫參數-->
<action
android:id="@+id/action_welcomeFragment_to_registerFragment"
app:destination="@id/registerFragment"
app:enterAnim="@anim/common_slide_in_right"
app:exitAnim="@anim/common_slide_out_left"
app:popEnterAnim="@anim/common_slide_in_left"
app:popExitAnim="@anim/common_slide_out_right"
app:popUpTo="@id/registerFragment"
app:popUpToInclusive="true"/>
</fragment>
WelcomeFragment.kt
btn_login.setOnClickListener {
//設置動畫參數
val navOption= navOptions {
anim {
enter = R.anim.common_slide_in_right
exit = R.anim.common_slide_out_left
popEnter = R.anim.common_slide_in_left
popExit = R.anim.common_slide_out_right
}
}
val action = WelcomeFragmentDirections.actionWelcomeFragmentToLoginFragment()
it.findNavController().navigate(action,navOption)
}
擴充該導航圖時,系統會解析這些操作,同時使用圖中定義的配置生成相應的 NavAction
對象。例如,action_welcomeFragment_to_registerFragment
定義爲從目的地 welcomeFragment 到目的地 registerFragment
的導航。該操作包含動畫以及 popTo
行爲,該行爲會從返回堆棧中移除所有目的地。所有這些設置都會以 NavOptions
形式捕獲並連接到 NavAction
。
要遵循此 NavAction
,請使用 NavController.navigate()
(傳遞action ID,不能使用destination ID ),否則無效
3.3 數據傳遞
官方文檔:https://developer.android.google.cn/guide/navigation/navigation-pass-data?hl=zh_cn
定義destination arguments
在Navigation Editor中點擊接收參數的descination 添加參數信息
xml代碼如下:
<fragment
android:id="@+id/registerFragment"
android:name="com.jetpack.ui.fragment.RegisterFragment"
android:label="RegisterFragment"
tools:layout="@layout/fragment_register">
<argument
android:name="EMAIL"
android:defaultValue="[email protected]"
app:argType="string" />
</fragment>
3.3.1 使用 Safe Args 傳遞安全的數據
WelcomeFragment 中設置參數:
btn_register.setOnClickListener {
val action = WelcomeFragmentDirections
.actionWelcomeToRegister("[email protected]")
findNavController().navigate(action)
}
RegisterFragment中接收參數:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val safeArgs: RegisterFragmentArgs by navArgs()
val email = safeArgs.EMAIL
}
3.3.2 使用Bundle對象傳遞參數
WelcomeFragment中設置參數:
btn_register.setOnClickListener {
var bundle = bundleOf("EMAIL" to "[email protected]")
findNavController().navigate(R.id.action_welcome_to_register, bundle)
}
RegisterFragment中接收參數:
arguments?.getString("EMAIL")