許多 Android 應用程式不需個別為每個畫面設定活動。事實上,許多常見的使用者介面模式 (如分頁標籤(tabs)) 均存在單一 activity 內,並使用名為「fragment」的組成部分。

fragment是可重複使用的使用者介面,並可嵌入一或多個 activity 中。在上方的螢幕截圖中,輕觸 tabs 並不會觸發顯示下一個畫面的 intent。而是,切換 tabs 僅僅是在先前 fragment 與原先 fragment 之間調換。這些事項都不需要啟動其他 activity。
您甚至可以在單一畫面上一次顯示多個 fragment,例如平板電腦裝置的主控制項詳細資料 layout。在以下範例中,左邊的導覽使用者介面和右側的內容都可以包含在不同的 fragment 中。兩個 fragment 在同一個 activity 中並存。

如您所見,fragment 是建構高品質應用程式的關鍵要素。在本程式碼研究室中,您將瞭解 fragment 的基本概念,並轉換 Word 應用程式來使用 fragment。也會瞭解如何使用 Jetpack Navigation component,以及使用名為 Navigation Graph 的新資源檔案,以在同一主機活動中導覽不同 fragment。完成本程式碼研究室後,您將獲得在下一個應用程式中導入 fragment 的基本技能。
學習目標
- fragment 生命週期 activity 生命週期的差異。
- 如何將現有 activity 轉換成 fragment。
- 如何在
Navigation Graph
中新增目的地,以及在使用Safe Args
外掛程式時在 fragment 之間傳遞資料。
範例程式碼
在本程式碼研究室中,在 activity 和 intent 程式碼研究室的結束時,您就能使用 Word 應用程式接續先前的進度。如果您已完成 activity 與 intent 程式碼研究室,請隨時使用您的程式碼作為起點。或者,直到此刻,您也可以從 GitHub 下載程式碼。
下載本程式碼研究室的範例程式碼
本程式碼研究室提供範例程式碼,可延伸至本程式碼研究室所教授的功能。範例程式碼可能包含先前介紹過的程式碼。也可能含有您不熟悉的程式碼,您可以在後續的程式碼研究室中學習。
如果您使用 GitHub 中的範例程式碼,請注意資料夾名稱是 android-basics-kotlin-words-app-activities
。在 Android Studio 中開啟專案時,請選取這個資料夾。
Fragment 與 Fragment 生命週期
片段(fragment)僅僅是一段可重複使用的應用程式使用者介面 如同 activity,fragment 具有生命週期且可回應使用者輸入。在畫面上顯示 activity 的 view 區塊階層內始終包含 fragment。由於 fragment 強調可重用性和模組化,甚至可以由單一 activity 同時代管多個 fragment,所以每個 fragment 都有各自的生命週期。
Fragment 生命週期
如同 activity,您也可以從記憶體初始化及移除 fragment,並且在 fragment 存在期間,會在螢幕上顯示、消失和重新顯示。此外,如同 activity,fragment 的生命週期有數個狀態,並提供多種覆寫方法,以回應 fragment 之間的轉換。fragment 生命週期為五個狀態,以 Lifecycle.State 列舉表示。
- INITIALIZED (已初始化): fragment 的新 instance 已實例化(instantiated)。
- CREATED (已建立): 呼叫第一個 fragment 生命週期方法。在此狀態下,系統也會建立與 fragment 相關聯的 view。
- STARTED (已啟動): fragment 在畫面上可見,但沒有焦點(focus),因此無法回應使用者輸入。
- RESUMED (已重新啟用): fragment 在畫面上可見,且有焦點(focus)。
- DESTROYED (已刪除): fragment 物件已解除實例化(de-instantiated)。
也類似於活動,Fragment 類別提供多種方法,讓您可回應於生命週期事件進行覆寫。
onCreate()
:fragment 已實例化(instantiated),並處於CREATED
狀態。不過,尚未建立對應的 view。onCreateView()
:這個方法是加載 layout 之處。fragment 已進入CREATED
狀態。onViewCreated()
:會在建立 view 後呼叫。在此方法中,您通常會呼叫findViewById()
來綁定(bind)特定 view 與屬性。onStart()
:fragment 已進入STARTED
狀態。onResume()
:fragment 已進入RESUMED
狀態且現在已聚焦(focus) (可回應使用者輸入)。onPause()
:fragment 已重新進入STARTED
狀態。使用者可看得到使用者介面onStop()
:fragment 已重新進入CREATED
狀態。物件已實例化(instantiated),但不再顯示在畫面上。onDestroyView()
:在 fragment 正好進入DESTROYED
狀態時呼叫。view 已從記憶體中移除,但fragment 物件仍然存在。onDestroy()
:fragment 進入DESTROYED
狀態。
下圖概述各種片段生命週期,以及各狀態之間的轉換。

生命週期狀態和 callback 方法非常類似於 activity 中使用的方法。不過,請記住與 onCreate()
方法的差異。配合 activity,使用此方法加載 layout 並 bind views。不過,在 fragment 生命週期內,系統會在建立 view 之前呼叫 onCreate()
,因此您無法在這裡加載 layout。請改為在 onCreateView()
中執行這項操作。建立 view 之後,系統會呼叫 onViewCreated()
方法,然後將屬性綁定(bind)至特定 view。
儘管似乎很理論,但您現在已經瞭解 fragment 的基本運作方式,以及與各 activity 的相似及相異之處。在本程式碼研究室的其餘部分,您將充分學以致用。首先,您必須遷移先前使用 Words 應用程式至使用 fragment 為基礎的版面配置。接下來,導入在單一 activity 中不同 fragment 之間的導覽(navigation)功能。
建立 Fragment 和 layout 檔案
如同 activity,您新增的每個 fragment 都包含兩個檔案:一個檔案是 layout 的 XML 檔案,另一個檔案則是顯示資料和處理使用者互動的 Kotlin 類別。您必須新增字母列表和字詞列表的 fragment。
- 在「Project Navigator」(專案導覽器) 中選取「app」(應用程式),並加入下列片段 (「File」(檔案) >「New」(新增) >「Fragment」(片段) >「Fragment (Blank)」(片段 (空白))),應該會產生各 fragment 的類別和 layout 檔案。
- 將第一個 fragment 的「Fragment Name」(fragment名稱) 設定為
LetterListFragment
。「Fragment Layout Name」(fragment layout 名稱) 應填入fragment_letter_list
。

- 將第二個 fragment 的「Fragment Name」(片段名稱) 設定為
WordListFragment
。「Fragment Layout Name」(fragment layout 名稱) 應填入fragment_word_list.xml
。

- 為這兩個 fragment 產生的 Kotlin 類別都包含許多範例程式碼,通常用於實作 fragment。不過,由於您是第一次使用 fragment,請從這兩個檔案中刪除所有內容,惟
LetterListFragment
和WordListFragment
的類別宣告除外。我們會引導您您從頭開始逐步實作片段,使您瞭解所有程式碼的運作方式。刪除範例程式碼後,Kotlin 檔案應像如下所示。
LetterListFragment.kt
1 | package com.example.wordsapp |
WordListFragment.kt
1 | package com.example.wordsapp |
- 將
activity_main.xml
的內容複製到fragment_letter_list.xml
,並將activity_detail.xml
的內容複製到fragment_word_list.xml
。將fragment_letter_list.xml
中的tools:context
更新為.LetterListFragment
,並將fragment_word_list.xml
中的tools:context
更新為.WordListFragment
。
變更後,片段版面配置檔案應像如下所示。
fragment_letter_list.xml
1 |
|
fragment_word_list.xml
1 |
|
導入 LetterListFragment
如同 activity,您需要加載 layout 置並綁定(bind)個別 view。使用 fragment 生命週期時,還是有些許差異。我們會引導您逐步完成 LetterListFragment
的設定程序,讓您有機會為 WordListFragment
進行相同設定。
若要在 LetterListFragment
中導入 view binding,您必須先取得 FragmentLetterListBinding
可為空值(nullable)的 reference(引用)。在 build.gradle 檔案的 buildFeatures
區段下啟用 viewBinding
屬性時,Android Studio 會為每個 layout 檔案產生與此類似的 Binding classes。您只需要為 FragmentLetterListBinding
中的每個 view 指派 fragment class 中的屬性。
type 應為 FragmentLetterListBinding?
,且初始值應為 null
。為什麼要使其可為空值?因為除非呼叫 onCreateView()
,否則您無法加載 layout。建立 LetterListFragment
的 instance (生命週期開始於 onCreate()
) 與到此屬性實際可用之間會有一段期間(period)。也請注意,您可以在 fragment 的生命週期內建立和刪除 fragment 的 view 數次。因此,您還必須重設在另一個生命週期方法 ( onDestroyView()
) 中的值。
- 在
LetterListFragment.kt
中,首先獲取對FragmentLetterListBinding
的引用(reference),並將引用(reference)命名為_binding
。
1 | private var _binding: FragmentLetterListBinding? = null |
它可為空值,因此每次存取 _binding
的屬性 (例如 _binding?.someView
) 時,您都必須納入 ?
來提供空值安全(null safety)。然而,這並不意謂您會因為一個空值,而捨棄有問號的程式碼。如果您確定某值在存取時不會為空值,則可以在類型名稱中附加 !!
。於是您就不需使用 ?
運算子,也能和任何其他屬性一樣進行存取。
- 建立名為
binding
的新屬性 (不含底線),並將其設為等於_binding!!
。
1 | private val binding get() = _binding!! |
此處,get()
意指屬性是「get-only」。這意指您可以「get」(取得) 這個值,但一旦指派 (如此處),就無法指派後給其他。
- 如要顯示 options menu,請覆寫
onCreate()
。在onCreate()
內呼叫setHasOptionsMenu()
並傳入true
。
1 | override fun onCreate(savedInstanceState: Bundle?) { |
- 請記住,使用 fragment 時,系統會在
onCreateView()
中加載 layout。加載 view、設定_binding
的值,並傳回 root view,就可導入onCreateView()
。
1 | override fun onCreateView( |
- 在
binding
屬性下方,建立 recycler view 的屬性(property)。
1 | private lateinit var recyclerView: RecyclerView |
- 然後在
onViewCreated()
中設定recyclerView
屬性的值,並呼叫chooseLayout()
,就像您在MainActivity
中的做法一樣。您很快就會將chooseLayout()
方法移到LetterListFragment
,所以不需擔心有錯誤。
1 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
請注意,binding class 已經建立 recyclerView
的屬性,因此您不需要針對每個 view 呼叫 findViewById()
。
- 最後在
onDestroyView()
中,將_binding
屬性重設為null
,因為 view 已不存在。
1 | override fun onDestroyView() { |
- 另外要注意的是,使用 fragment 時,
onCreateOptionsMenu()
方法有些微的差異。雖然Activity
class 具有名為menuInflater
的全域屬性(global property),但Fragment
並未提供這項屬性(property),而是將 menu inflater 傳遞至onCreateOptionsMenu()
中。另請注意,搭配 fragment 使用的onCreateOptionsMenu()
方法不需要回傳 statement。實作方法如下所示:
1 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { |
- 從
MainActivity
中按原樣移動chooseLayout()
、setIcon()
和onOptionsItemSelected()
的其餘程式碼。應注意的唯一差別在於,與 activity 不同,fragment 不是Context
。您無法傳入this
(指 fragment object) 做為 layout manager 的 context。但是,fragment 會提供context
屬性,您可以改用該屬性。程式碼的其餘部分與MainActivity
相同。
1 | private fun chooseLayout() { |
- 最後,複製
MainActivity
的isLinearLayoutManager
屬性。將此屬性放在recyclerView
屬性的宣告正下方。
1 | private var isLinearLayoutManager = true |
- 現在所有功能都已經移至
LetterListFragment
,MainActivity
class 所需要做的只是加載 layout,使得 fragment 顯示在 view 中。繼續從MainActivity
刪除所有內容,惟onCreate()
除外。變更後,MainActivity
只能包含下列項目。
1 | override fun onCreate(savedInstanceState: Bundle?) { |
將 DetailActivity 轉換為 WordListFragment
就是將 DetailActivity
轉換為 WordListFragment
和將 MainActivity
遷移至 LettersListFragment
的遷移作業幾乎相同。請執行下列步驟,將程式碼遷移至 WordListFragment
。
- 首先,請將 companion object 複製到
WordListFragment
。
1 | companion object { |
- 然後在
LetterAdapter
中,在執行 intent 的onClickListener()
中,您必須將呼叫更新為putExtra()
,將DetailActivity.LETTER
替換為WordListFragment.LETTER
。
1 | intent.putExtra(WordListFragment.LETTER, holder.button.text.toString()) |
- 同樣地,在
WordAdapter
中,在您前往字詞的搜尋結果時必須更新出現onClickListener()
,並將DetailActivity.SEARCH_PREFIX
替換為WordListFragment.SEARCH_PREFIX
。
1 | val queryUrl: Uri = Uri.parse("${WordListFragment.SEARCH_PREFIX}${item}") |
- 返回
WordListFragment
,新增 type 為FragmentWordListBinding?
的_binding
變數。變數應可為空值,並將null
作為初始值。
1 | private var _binding: FragmentWordListBinding? = null |
- 然後新增名為
binding
的 get-only 變數,這樣無需使用?
就能引用 view。
1 | private val binding get() = _binding!! |
- 接著,請調整 layout,指派
_binding
變數並傳回 root view。請記得,對於 fragment,您在onCreateView()
中執行此作業,而不是onCreate()
。
1 | override fun onCreateView( |
- 接下來,您要導入
onViewCreated()
。這幾乎和在DetailActivity
中的onCreate()
中設定recyclerView
相同。不過,由於 fragment 無法直接存取 intent,因此您必須使用activity.intent
進行引用。但是,您必須在onViewCreated()
中執行此作業,因為無法保證在生命週期早期已存在 activity。
1 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
- 最後,您可以在
onDestroyView()
中將_binding
重設為空值(null)。
1 | override fun onDestroy() { |
- 刪除
DetailActivity
中的其餘程式碼,只保留onCreate()
方法。
1 | override fun onCreate(savedInstanceState: Bundle?) { |
移除 DetailActivity
現在,您已順利將 DetailActivity
的功能遷移至 WordListFragment
,所以不再需要 DetailActivity
。您可以繼續刪除 DetailActivity.kt
和 activity_detail.xml
,也可以對資訊清單進行小幅變更。
- 首先,請刪除
DetailActivity.kt

- 確認已取消勾選「Safe Delete」,然後按一下「OK」。

- 接著刪除
activity_detail.xml
。再次確認已取消勾選「Safe Delete」(安全刪除)。

- 最後,由於
DetailActivity
已不存在,請將下列項目從AndroidManifest.xml
中移除。
1 | <activity |
刪除 DetailActivity
後,剩下兩個 fragment ( LetterListFragment
和 WordListFragment
) 和單一 activity ( MainActivity
)。下一節將介紹 Jetpack 導覽元件(navigation component) 並編輯 activity_main.xml
,使得能夠顯示 fragment 及在 fragment 之間導覽,而不是代管靜態 layout。
Jetpack 導覽元件
Android Jetpack 提供導覽元件(Navigation component),可協助您在應用程式中處理任何簡易或複雜的導覽(navigation)實作。Navigation component 包含三個主要部分,可供您在 Words 應用程式中導入 navigation 功能。
Navigation Graph
:Navigation Graph
是可以在您的應用程式中提供 navigation 功能視覺表示的 XML 檔案。檔案包含對應於個別 activity 和 fragment 的「目的地」,以及在程式碼中可用於在目的地之間導覽(navigation)的動作。就如同 layout 檔案,Android Studio 提供視覺編輯器,可用於在Navigation Graph
中加入目的地和動作。NavHost
:NavHost
是用來顯示在 activity 內來自Navigation Graph
的目的地。當您在 fragment 之間導覽時,NavHost
中顯示的目的地也會隨之更新。您需要在MainActivity
中使用內建的實作,名為NavHostFragment
。NavController
:NavController
物件(object)可讓您控制NavHost
中顯示的目的地之間的導覽(navigation)動作。使用 intent 時,您必須呼叫startActivity
才能前往新的畫面。您可以使用 navigation component 呼叫NavController
的navigate()
方法,調換所顯示的 fragment。NavController
也有助於您處理一般工作,例如回應系統「up」按鈕,即可返回先前顯示的 fragment。
Navigation Dependency
- 在 project-level 的
build.gradle
檔案中,於 buildscript > ext 的material_version
下方,將nav_version
設為等於2.5.2
。
1 | buildscript { |
- 在 app-level 的
build.gradle
檔案中,將以下內容加入 dependencies 群組:
1 | implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" |
Safe Args Plugin(外掛程式)
初次在 Words 應用程式中導入 navigation 時,您使用這兩種 activity 之間的 explicit intent(明確意圖)。若要在兩個 activity 之間傳遞資料,您必須呼叫 putExtra()
方法,並傳入所選字母。
將 navigation component 導入至 Words 應用程式之前,建議您也新增名為 Safe Args 的 Gradle plugin(外掛程式),協助您在 fragment 之間傳遞資料時確保 type 安全。
請執行下列步驟,將 SafeArgs 整合至您的專案
- 在 project-level 的
build.gradle
檔案中,於 buildscript > dependencies 新增下列類別路徑。
1 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" |
- 在 app-level 的
build.gradle
檔案中,在plugins
內的頂端新增androidx.navigation.safeargs.kotlin
。
1 | plugins { |
- 編輯 Gradle 檔案後,頁面頂端會顯示黃色橫幅,要求您同步處理專案。按一下「Sync Now」,等待一兩分鐘讓 Gradle 更新專案的 dependencies,反映所做變更。

同步完成後,您就可以進行下一步來新增 navigation graph。
使用 Navigation Graph
現在您對 fragments 和生命週期都有基本瞭解,接著就要開始更有趣的事情。下一步是納入 Navigation component。Navigation component 只是一系列導入尤其在 fragments 之間 navigation 的工具集合。您將使用新的 visual editor 協助導入 fragments 之間的 navigation;Navigation Graph
(簡稱 NavGraph
)。
什麼是 Navigation Graph?
Navigation Graph
(簡稱 NavGraph
) 是應用程式 navigation 的虛擬對應。在這種情況下,每個 screen 或 fragment 都變成一個可以前往的可能「目的地」。NavGraph
能以 XML 檔案表示,表明每個目的地彼此之間有何關聯。
這項功能會在幕後建立 NavGraph
class 的新 instance。不過,FragmentContainerView
會向使用者顯示 navigation graph 中的目的地。您只需建立 XML 檔案並定義可能的目的地即可。然後使用產生的程式碼以在 fragment 之間導覽。
在 MainActivity 中使用 FragmentContainerView
由於 layouts 已包含在 fragment_letter_list.xml
和 fragment_word_list.xml
中,因此 activity_main.xml
檔案不再需要包含應用程式中第一個畫面(screen)的 layout。而是重複利用 MainActivity
以包含 FragmentContainerView
來作為 fragment 的 NavHost
。從現在開始,應用程式中的所有 navigation 動作都會發生在 FragmentContainerView
中。
- 將
activity_main.xml
(即androidx.recyclerview.widget.RecyclerView
) 中的FrameLayout
內容替換為FragmentContainerView
。將此 ID 設定為nav_host_fragment
,並將高度和寬度設定為match_parent
,即可填滿整個 frame layout。
將下面的 code:
1 | <androidx.recyclerview.widget.RecyclerView |
替換為此:
1 | <androidx.fragment.app.FragmentContainerView |
- 在 ID 屬性下方新增
name
屬性,並設定為androidx.navigation.fragment.NavHostFragment
。雖然您可以針對此屬性指定特定 fragment,但設定為NavHostFragment
即可讓FragmentContainerView
在 fragment 之間導覽。
1 | android:name="androidx.navigation.fragment.NavHostFragment" |
- 在
layout_height
和layout_width
屬性下方新增app:defaultNavHost
屬性,並設定為等於 “true”。如此一來,fragment container 就可以與 navigation 階層互動。舉例來說,按下系統返回(back)按鈕後,container 就會回到先前顯示的 fragment,就像顯示新 activity 時的情況一樣。
1 | app:defaultNavHost="true" |
- 新增名為
app:navGraph
的屬性,並將該屬性設定為等於 “@navigation/nav_graph”。這會指向一個 XML 檔案,用於定義應用程式 fragment 之間的導覽方式。Android Studio 暫時會顯示尚未解決的符號錯誤。您會在接下來的工作中解決這個問題。
1 | app:navGraph="@navigation/nav_graph" |
- 最後,由於您使用應用程式 namespace 新增了兩項屬性,因此務必在
FrameLayout
中加入xmlns:app
屬性。
1 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
這些就是 activity_main.xml
中的所有變更。接下來,您將建立 nav_graph
檔案。
設定 Navigation Graph
新增 navigation graph 檔案 (「File」(檔案)>「New」(新增) >「Android Resource File」(Android 資源檔案)),並按照以下方式填寫各欄位。
- 檔案名稱:
nav_graph.xml
這個名稱與您為app:navGraph
屬性設定的名稱相同。 - 資源類型:「Navigation」。系統隨即會將「Directory Name」自動變更為 navigation,然後建立名為「navigation」的新資源資料夾。

建立 XML 檔案時,系統會顯示新的視覺編輯器。由於您已引用 FragmentContainerView
的 navGraph
屬性中的 nav_graph
,如要新增目的地,請按一下畫面左上方的新增按鈕,然後為每個 fragment 建立目的地 (一個用於 fragment_letter_list
,另一個用於 fragment_word_list
)。

新增完成後,這些片段應該就會顯示在畫面中央的 navigation gragh 上。您也可以使用左側顯示的元件樹狀結構來選取特定目的地。
建立 Navigation action(動作)
如要建立 letterListFragment
到 wordListFragment
目的地之間的導覽動作,請將滑鼠游標懸停在 letterListFragment
目的地上,然後從右側顯示的圓圈拖曳至 wordListFragment
目的地。

現在您應該會看到已建立的箭頭,用來表示兩個目的地之間的動作。按一下箭頭,您就會在屬性窗格中看到可在程式碼中引用名稱為 action_letterListFragment_to_wordListFragment
的動作。
指定 WordListFragment 的 Arguments(參數)
使用 intent 在 activity 之間導覽(navigation)時,您指定了「extra」(額外項目),使得所選字母可傳遞到 wordListFragment
。Navigation 也支援在目的地之間傳遞參數,並以 type 安全的方式完成這項操作。
選取 wordListFragment
目的地,然後在屬性窗格的 Arguments下方,按一下「+」按鈕建立新的參數(argument)。
參數(argument)應名為 letter
,型別應為 String
。您先前新增的 Safe Args 外掛程式就能派上用場。將這個引數(argument)指定為字串(String),可確保在程式碼中執行 navigation 動作時,String 會如預期運作。

設定起始目的地
當您的 NavGraph
知道所有需要的目的地時,FragmentContainerView
如何知道首先要顯示哪個 fragment? 在 NavGraph
中,您需要將字母列表設定為起始目的地。
如要設定起始目的地,請選取 letterListFragment
,然後按一下「Assign start destination」按鈕。

這就是您目前需要在 NavGraph
編輯器執行的所有操作。此時,您就可以繼續並建立專案。在 Android Studio 中,從選單列依序選取「Build」>「Rebuild Project」。系統會根據您的 navigation graph 產生一些程式碼,方便您使用剛建立的 navigation 動作。
執行 Navigation Action
開啟 LetterAdapter.kt
以執行 navigation action(動作)。只需要兩個步驟即可完成。
- 刪除按鈕
setOnClickListener()
的內容。您必須改為擷取剛建立的 navigation action。將以下內容新增至setOnClickListener()
。
1 | val action = LetterListFragmentDirections.actionLetterListFragmentToWordListFragment(letter = holder.button.text.toString()) |
系統可能無法辨識部分的類別和函式名稱,原因是在建構專案後已自動產生這些名稱。如此一來,您在第一個步驟新增的 Safe Args 外掛程式就能派上用場,而在 NavGraph
中建立的動作(action)會轉為您可以使用的程式碼。然而,這些名稱應該要相當直覺易懂。LetterListFragmentDirections
表示從 letterListFragment
為起點開始的所有可能 navigation 路徑。
actionLetterListFragmentToWordListFragment()
函式是前往 wordListFragment
的特定動作。
對 navigation action 進行引用後,只需要取得「NavController」(可用來執行 navigation action 的物件) 的引用並呼叫 navigate()
傳入該動作(action)即可。
1 | holder.view.findNavController().navigate(action) |
設定 MainActivity
最後一段設定位於 MainActivity
中。僅需要少許變更 MainActivity
就可確保一切運作正常。
- 建立
navController
屬性。此標記為lateinit
,因為它會在onCreate
中進行設定。
1 | private lateinit var navController: NavController |
- 然後,在
onCreate()
中呼叫setContentView()
後,引用nav_host_fragment
(這是FragmentContainerView
的 ID),並指派給navController
屬性。
1 | val navHostFragment = supportFragmentManager |
- 然後,在
onCreate()
中呼叫setupActionBarWithNavController()
,並傳入navController
。這可確保畫面上會顯示動作列(action bar/app bar) 按鈕(例如LetterListFragment
中的 menu 選項)可見。
1 | setupActionBarWithNavController(navController) |
- 最後,導入
onSupportNavigateUp()
。除了在 XML 中將defaultNavHost
設定為true
之外,此方法也可用來處理 up 按鈕。不過,您的 activity 必須提供實作。
1 | override fun onSupportNavigateUp(): Boolean { |
到目前為止,所有元件就緒,可以使用 fragment 進行導覽(navigation)。不過,現在 navigation 功能是使用 fragment 而非 intent 執行,因此您在 WordListFragment
中使用的字母的 intent extras 將不再有效。在下一個步驟中,您必須更新 WordListFragment
以取得 letter
參數。
獲取 WordListFragment 中的參數
您先前曾在 WordListFragment
中引用 activity?.intent
以存取 letter
extra。這雖然有效,但不是最佳做法,因為 fragment 可以嵌入在其他 layout 中及大型應用程式中,這會很難假設 fragment 屬於哪個 activity。此外,使用 nav_graph
執行導覽且使用安全參數時是沒有 intent 的,因此嘗試存取 intent extra 並不可行。
幸好,存取安全參數非常簡單,無須等到 onViewCreated()
呼叫完畢。
- 在
WordListFragment
中建立letterId
屬性。您可以將此屬性標記為lateinit
,這樣就不必將它設為可為 null 了。
1 | private lateinit var letterId: String |
- 接著覆寫
onCreate()
(不是onCreateView()
或onViewCreated()!
),並加入以下內容:
1 | override fun onCreate(savedInstanceState: Bundle?) { |
由於 arguments
有可能是可選的,因此請務必呼叫 let()
並傳入 lambda
。這段程式碼會假設 arguments
不是空值(null),並傳入 it
參數的非空值(null)參數。不過,如果 arguments
是 null
,則 lambda 將不會執行。

雖然 Android Studio 不是實際程式碼之部分,但還提供實用的提示,讓您瞭解 it
參數。
Bundle
到底是什麼?可將其視為用在類別 (例如 activity 和 fragment) 之間傳遞資料的 key/value 組合。實際上,當您在這個應用程式的第一個版本中執行 intent 時,呼叫 intent?.extras?.getString()
時使用的就是 bundle。和使用 fragment 時,從參數中取得字串的方式完全相同。
- 最後,您可以在設定 recycler view 的 adapter 時存取
letterId
。將onViewCreated()
中的activity?.intent?.extras?.getString(LETTER).toString()
替換成letterId
。
1 | recyclerView.adapter = WordAdapter(letterId, requireContext()) |
您成功了!請花點時間執行應用程式。現在,您可以在一個 activity 中,在兩個畫面之間進行導覽(navigation),而不需要使用任何 intent。
更新 Fragment Label
您已成功將這兩個畫面轉換成使用 fragment。在進行變更之前,每個 fragment 的 app bar 都會針對 app bar 中的每個 activity 提供描述性標題(descriptive title)。然而,一旦轉換成使用 fragment,這個標題就不會出現在 detail activity 中。

fragment 有一個名為 label
的屬性,您可以在其中設定父項 activity 要在 app bar 中使用的標題。
- 在
strings.xml
中,在 app 名稱之後,新增下列常數。
1 | <string name="word_list_fragment_label">Words That Start With {letter}</string> |
- 您可以在 navigation graph 中設定每個 fragment 的 label。返回
nav_graph.xml
中,在 component 樹狀結構中選取letterListFragment
,然後前往屬性窗格將 label 設定為app_name
字串:

- 選取
wordListFragment
,並將 label 設定為word_list_fragment_label
:

恭喜您完成目前為止的工作!請再執行一次應用程式,您應該會看到程式碼研究室開始時呈現的原貌,不過現在所有 navigation 動作都已透過單一 activity 代管,且每個畫面都設有獨立的 fragment。

總結
- fragment 是可嵌入 activity 中可重複使用的使用者介面。
- fragment 的生命週期與 activity 生命週期不同,而 view 設定發生在
onViewCreated()
中,而不是onCreateView()
。 FragmentContainerView
可用來將 fragment 嵌入至其他 activity 中,以及管理 fragment 之間的 navigation。
使用 navigation component
- 設定
FragmentContainerView
的navGraph
屬性可讓您在 activity 內的不同 fragment 之間進行 navigation。 NavGraph
編輯器可讓您新增 navigation 動作,以及指定不同目的地的之間的參數(argument)。- 使用 intent 進行 navigation 時,您必須傳入 extra,navigation component 會使用 SafeArgs 自動為 navigation 動作產生類別和方法,以確保參數(argument)的 type 安全性。
fragment 的用途
- 使用 navigation component 時,許多應用程式可在單一 activity 中管理整個 layout,且所有navigation 動作都在 fragment 之間進行。
- fragment 可讓您使用常見的 layout 模式,例如平板電腦上的 master-detail layout,或是相同 activity 中的多個 fragment label。