---
# System prepended metadata

title: Android 筆記 -- Navigation (一) 導航組件
tags: [android, Fragment, navigation]

---

<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 等級 到指定水平。
>![image](https://hackmd.io/_uploads/S16rrhpZ1l.png =40%x)

</div>

---
<h2>▼Navigation 的構成: Graph、Host、Controller</h2>
---

<h3><font size=4><b>&lt; <u>Navigation Graph(導航圖)</u> &gt;</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>

![image](https://hackmd.io/_uploads/SyZ5KnTWkl.png)
![image](https://hackmd.io/_uploads/rka0F36Zkg.png)

</div>
</details>
<details><summary>基本使用</summary>
<div>

---
- 1. 在導覽圖頁面中，點選 new Desttination 可以在畫面中新增 Fragment 或 Activity 到頁面中
  ![image](https://hackmd.io/_uploads/HyvhjnTWyl.png)

- 2. 新增後就可以牽線，牽線會生成 <code>action</code>，這個 <code>action</code> 代表了我們想從 <code>FragmentA</code> 前往 <code>FragmentB</code> 的動作，我們會在 <code>Controller</code> 使用到他
  ![1731215029990](https://hackmd.io/_uploads/HkkjibAZJg.gif)

</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>&lt; <u>Navigation Host(導航容器)</u> &gt;</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> 就好了
![image](https://hackmd.io/_uploads/rJY7gg1f1e.png)
</br>
![image](https://hackmd.io/_uploads/rJjKxxkM1e.png)
- <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>&lt; <u>Navigation Controller(導航控制器)</u> &gt;</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>)，如果當前導航圖本身就在最底部了，那會跳出程式。
![image](https://hackmd.io/_uploads/HyONanCzkx.png)

</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)