<h1>Android 筆記 -- Navigation (一) 導航組件</h1>
>前置閱讀: [Android筆記–Fragment](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_Fragment)
<code>Navigation</code> 是用來管理 App 內畫面之間切換與流程控制的元件,常用於處理 Fragment 或 Jetpack Compose 畫面的導覽。它可以統一管理頁面切換、參數傳遞、返回堆疊,以及深層連結(Deep Link)等功能。
相較於傳統 <code>FragmentTransaction</code>、<code>Bundle</code> 與 <code>Back Stack</code> 等做法,<code>Navigation</code> 提供更結構化且易於維護的方式,能降低程式複雜度,提升可讀性與開發效率。
<details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary><code>Navagation</code> 和 <code>intent</code> 的頁面跳轉差在哪裡?</summary>
<div>
- <code><b>Navagation</b></code>:
==專為 Fragment 導航 而設計==,通常在單一 Activity 應用中使用。Navigation 透過一個導航圖(Navigation Graph) 集中管理頁面之間的切換,讓導航結構清晰易讀。
- <code><b>Intent</b></code>:
是 Android 原生的頁面跳轉方式,==適用於 Activity 之間 的導航==,且支援跨應用跳轉。主要依賴 Intent 的參數設定和系統的 Activity 管理來執行跳轉。
</div>
</details>
---
<h2>▼宣告依附元件</h2>
---
到官方網站找適當的依附元件 -- [AndroidDeveloper--Navigation](https://developer.android.com/jetpack/androidx/releases/navigation?hl=zh-tw#kts)
```kotlin=
dependencies {
val nav_version = "2.8.0"
// Jetpack Compose integration
implementation("androidx.navigation:navigation-compose:$nav_version")
// Views/Fragments integration
implementation("androidx.navigation:navigation-fragment:$nav_version")
implementation("androidx.navigation:navigation-ui:$nav_version")
}
```
<div id="dependNote">
>[!Warning]備註
>如果嘗試不宣告依附就直接新增 <code>NavGraph</code> 的話,系統會跳出警告視窗,選擇確定的話會自動加載相關元件。使用此方法可能會造成 API 等級 錯誤,因此需要調整 API 等級 到指定水平。
>
</div>
---
<h2>▼Navigation 的構成: Graph、Host、Controller</h2>
---
<h3><font size=4><b>< <u>Navigation Graph(導航圖)</u> ></b></font></h3>
是一個 XML 資源文件,描述應用內所有可導航的頁面(Fragment 或 Activity)以及它們之間的關係。定義了導航的路徑、條件、動作和參數。
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary>新增導航圖</summary>
<div>


</div>
</details>
<details><summary>基本使用</summary>
<div>
---
- 1. 在導覽圖頁面中,點選 new Desttination 可以在畫面中新增 Fragment 或 Activity 到頁面中

- 2. 新增後就可以牽線,牽線會生成 <code>action</code>,這個 <code>action</code> 代表了我們想從 <code>FragmentA</code> 前往 <code>FragmentB</code> 的動作,我們會在 <code>Controller</code> 使用到他

</div>
</details>
<details id="NavigationGraphBasicProperties"><summary>NavigationGraph 基本屬性</summary>
<div>
---
- <b><code>app:startDestination</code></b>: 定義導航圖的起始目的地,即應用開啟時最先顯示的頁面。
- <b><code>android:name</code></b>: 指定目的地的完整類名(如 Fragment 或 Activity 的類名)。
- <b><code>app:destination</code></b>: 指定導航行為的目的地,即目標頁面的 ID。
- <b><code>android:label</code></b>: 設置目的地的標題,通常用於顯示在工具列或導航欄上。
- <b><code>action 元素</code></b>: 設置從一個 Fragment 到另一個 Fragment 的具體導航邏輯。
- <b><code>deepLink 元素</code></b>: 深層鏈接的作用是讓應用可以直接透過特定的 URL 或 URI 路徑,直接導航到應用中的某個頁面,而不需要用戶逐層點擊進入。例如:通過 Deep Link 將用戶導向應用的特定頁面。
</div>
</details>
<details><summary>NavigationGraph 進階屬性</summary>
<div>
---
- <b><code>app:popUpTo</code></b>: 在導航到新目的地之前,清空回退堆疊直到指定的目的地。例如:當完成某些任務後(如登入),不希望用戶返回前一頁時使用。
- <b><code>app:popUpToInclusive</code></b>: 當設為 true 時,會包含 popUpTo 目的地本身一同清除;設為 false 則保留該目的地。通常配合 app:popUpTo 使用,以決定是否保留指定頁面。
- <b><code>app:enterAnim</code>/<code>app:exitAnim</code></b>: 定義進入和退出動畫效果,用於頁面切換時的動畫過渡。
- <b><code>app:popEnterAnim</code>/<code>app:popExitAnim</code></b>: 定義當回退操作(popBackStack)發生時的進入和退出動畫效果。設置自定義的回退動畫,例如回到上一頁時的淡入淡出效果。
- <b><code>argument 元素</code></b>: 在需要傳遞資料(如產品 ID 或用戶資訊)時使用,用於定義從一個頁面傳遞到另一個頁面的參數,支援類型(如 Int、String、Boolean)和預設值。
```xml=
<fragment
android:id="@+id/detailsFragment"
android:name="com.example.app.DetailsFragment"
android:label="Details">
<argument
android:name="productId"
app:argType="integer"
android:defaultValue="0" />
</fragment>
```
- <b><code>app:launchSingleTop</code></b>: 設置為 <code>true</code> 時,若當前目的地已在堆疊頂部則不會重新載入新的目的地,防止重複導航。
- <b><code>app:restoreState</code></b>: 設置為 <code>true</code> 時,當導航至目的地且該目的地在回退堆疊中時會保留其狀態。例如在切換頁面後返回原頁面時希望保持當前狀態(如滾動位置或輸入框內容)。
</div>
</details>
</br>
<details><summary>NavigationGraph 範例</summary>
<div>
```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"
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.mypratice_navigation_2.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.mypratice_navigation_2.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.mypratice_navigation_2.FragmentC"
android:label="fragment_c"
tools:layout="@layout/fragment_c" />
</navigation>
```
</div>
</details>
</div>
---
<h3><font size=4><b>< <u>Navigation Host(導航容器)</u> ></b></font></h3>
是一個容器,類似於 [FragmentContainer](https://hackmd.io/V5JVVbtSRsysKGD2Cme4Ag?view=&stext=2024%3A22%3A0%3A1731210895%3AGYA68m),用來顯示導航圖中的目的地。
通常是使用 <code>NavHostFragment</code> (<code>NavHostFragment</code> 是 Fragment 的子類別)。它通過 <code>NavController</code> 來控制導航操作。
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary>基本使用</summary>
<div>
---
在 Activity 新增 <code>NavHostFragment compoment</code> 並指定要使用的 <code>NavGraph</code> 就好了

</br>

- <b><code>app:navGraph</code></b>: 指定了 <code>NavHostFragment</code> 使用的導航圖的 ID,該屬性連接導航圖,從而載入導航配置。
- <b><code>app:defaultNavHost</code></b>: 設置為 <code>true</code> 時,<code>NavHostFragment</code> 成為應用的預設導航宿主,處理返回按鈕行為,用於當前 Activity 只有一個 <code>NavHostFragment</code> 時,讓 <code>NavHostFragment</code> 處理返回按鈕。
</div>
</details>
</div>
---
<h3><font size=4><b>< <u>Navigation Controller(導航控制器)</u> ></b></font></h3>
是 <code>Navigation</code> 的控制器,我們可以使用 <code>NavController</code> 來觸發導航操作負責管理 <code>NavHostFragment</code> 的導航圖,處理各個頁面之間的切換和回退邏輯
- 例如: 從一個 Fragment 跳轉到另一個 Fragment。
- 除了頁面跳轉,還可以做到返回堆疊管理、資料傳遞等。
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary>取得 <code>NavController</code> -- 傳統基礎作法</summary>
<div>
---
- <b>步驟1.</b> 取得 Activity 中的 <code>NavHostFragment</code>
```kotlin=
val navHostFragment = requireActivity().supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
//NavHostFragment的是Fragment的子類別,因此可以透過requireActivity().supportFragmentManager.findFragmentById()來找到他
```
- <b>步驟2.</b> 從 <code>NavHostFragment</code> 取得 <code>NavController</code>
```kotlin=
val navController = navHostFragment.navController
```
- <b>步驟3. </b>根據需求使用 <code>NavController</code>,例如 Fragment 跳轉
```kotlin=
binding.button1.setOnClickListener {
navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
```
</div>
</details>
<details><summary>取得 <code>NavController</code> -- 使用 <code>fragment-ktx 擴展函數</code> 的作法</summary>
<div>
---
優點: 類型安全、編譯時期錯誤檢查、簡潔且易維護、支持 <code>Safe Args</code> 傳遞參數
>如果在宣告附加元件時使用警告視窗的自動加載元件的話,<code>fragment-ktx</code> 也會被加載(也就是說 步驟1 可以省略),[參見](#dependNote)
- <b>步驟1.</b> 加載 Fragment 的 <code>Ktx 擴展函數</code>
[AndroidDeveloper--Fragment依賴元件](https://developer.android.com/jetpack/androidx/releases/fragment?hl=zh-tw#declaring_dependencies)
```kotlin=
implementation ("androidx.fragment:fragment-ktx:1.8.3")
```
- <u><b>步驟2.</b> 直接在需要 <code>NavController</code> 的地方用 <code>findNavController()</code> 方法</u>
```kotlin=
class FragmentA : Fragment() {
private lateinit var binding: FragmentABinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
binding = FragmentABinding.inflate(inflater, container, false)
val navController = findNavController()
binding.button1.setOnClickListener {
navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
return binding.root
}
}
```
</div>
</details>
<details><summary>在 Activity 中取得 <code>NavController</code></summary>
<div>
---
在 Activity 中取得 <code>NavController</code> 的方法和在 Fragment 中略有不同,基本上一樣是從 <code>NavHostFragment</code> 取得,須注意用 <code>ViewBinding</code> 的話,要再加上 <code>getFragment<NavHostFragment>()</code>
```kotlin=
//在Activity取得NavController,A方法
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
val navController = navHostFragment.findNavController()
//在Activity取得NavController,B方法
val navHostFragment = binding.fragmentContainerView.getFragment<NavHostFragment>()
val navController = navHostFragment.findNavController()
```
</div>
</details>
</br>
<details><summary><code>NavController</code> 中常用的方法</summary>
<div>
---
- <b><code>navigate(destinationId: Int)</code></b>: 最基本且常見的方法,將使用者導航到指定的目的地。
```kotlin=
navController.navigate(R.id.action_fragmentA_to_fragmentB)
```
- <b><code>navigateUp()</code></b>: 嘗試將使用者導引回上一個目的地。如果無法導航回上一個頁面,它會返回 false。
- <b><code>popBackStack(destinationId: Int, inclusive: Boolean)</code></b>: 從返回堆疊中彈出指定頁面,回到特定的目的地。<code>inclusive</code> 決定是否也彈出 destinationId 自己。
- <b><code>getBackStackEntry(destinationId: Int)</code></b>: 取得指定目的地的返回堆疊條目,用於從不同頁面共用狀態,例如: 共享 <code>ViewModel</code>。
- <b><code>currentBackStackEntry</code></b>: 獲取當前的返回堆疊條目,常用於觀察當前頁面狀態變化,例如: 觀察當前頁面的 <code>LiveData</code>。
```kotlin=
navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(viewLifecycleOwner) { value ->
// Handle value change
}
```
- <b><code>navigate(route: String)</code></b>: 使用字串路由導航(常用於 Compose),比 ID 導航更靈活,可以攜帶動態參數。
```kotlin=
navController.navigate("profile/{userId}")
```
- <b><code>setGraph(navGraphId: Int)</code></b>: 動態設定或重設導航圖,適合需要依照條件載入不同導航圖的情境,例如多入口點應用。
```kotlin=
navController.setGraph(R.navigation.new_nav_graph)
```
- <b><code>restoreState(savedState: Bundle?)</code></b>: 將保存的導航狀態恢復到 NavController,常用於在系統重建或頁面重啟時還原導航狀態。
- <b><code>saveState()</code></b>: 儲存當前的導航狀態,通常與 restoreState 搭配使用來還原頁面和導航狀態。
</div>
</details>
</div>
---
▼Q&A
---
<details><summary>Q: 如果我導航圖中的 <code>popUpTo</code> 中,填入了導航圖本身會怎麼樣?</summary>
<div>
---
Ans:會跳出當前所在的導航圖
例如: 在 <code>action_fragmentB2_to_fragmentD2</code> 的 <code>popUpTo</code> 屬性上填入導航圖本身(也就是從 <code>nav_graph_a</code> 跳到 <code>nav_graph</code>),如果當前導航圖本身就在最底部了,那會跳出程式。

</div>
</details>
---
▼GitHub 專案
---
[MyPratice_Navigation_1](https://github.com/PudCheetah/MyPratice_Navigation_1.git)
---
下一篇: [Android 筆記– Navigation (二)使用 Safe Args 傳遞資料](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_Navigation_SafeArgs)
---
▼參考資料:
---
- [Jetpack-Navigation](https://cheenjame.medium.com/jetpack-navigation-7ca23543dec3)
- [AndroidDeveloper--導航](https://developer.android.com/guide/navigation?hl=zh-tw)
- [Day 23 Navigation (一) 介紹與基本使用](https://ithelp.ithome.com.tw/articles/10225937)
- [Android Navigation 學習筆記(一) 基礎使用](https://medium.com/@wsrew2000/android-navigation-%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-%E4%B8%80-%E5%9F%BA%E7%A4%8E%E4%BD%BF%E7%94%A8-3c1607ce4d38)
- [Android Kotlin/Java Jetpack Navigation 介紹、入門教學example](https://willy2016.pixnet.net/blog/post/216916713)
- [Navigation (1)](https://ithelp.ithome.com.tw/articles/10276248)
- [Navigation (2)](https://ithelp.ithome.com.tw/articles/10276786)