FragmentManager
是負責對應用程式片段執行動作的類別,例如新增、移除或取代片段,並將片段新增至返回堆疊。
如果採用 Jetpack Navigation 程式庫,您可能不會與 FragmentManager
直接互動,因為該程式庫會代您進行 FragmentManager
的相關作業。不過,任何使用片段的應用程式都會在某些程度上使用 FragmentManager
,因此請務必瞭解這項類別及其運作方式。
本頁面說明以下內容:
- 如何存取
FragmentManager
。 - 與活動和片段相關的
FragmentManager
角色。 - 如何使用
FragmentManager
管理返回堆疊。 - 如何為片段提供資料和依附元件。
存取 FragmentManager
您可以透過活動或片段存取 FragmentManager
。
FragmentActivity
及其子類別 (例如 AppCompatActivity
) 都能透過 getSupportFragmentManager()
方法存取 FragmentManager
。
片段可代管一或多個子項片段。在片段中,您可以透過 getChildFragmentManager()
取得 FragmentManager
參照,用來管理片段的子項。如果需要存取主機 FragmentManager
,可以使用 getParentFragmentManager()
。
以下舉幾個例子來瞭解
片段、其主機,以及相關聯的 FragmentManager
例項
各自的經驗值
圖 1 顯示兩個範例,每個範例都只有一個活動主機。這兩個範例中的主機活動都會以 BottomNavigationView
的形式向使用者顯示頂層導覽,該元素負責在應用程式中使用不同的畫面替換主機片段。每個畫面都會實作為獨立的片段。
範例 1 主機片段代管的是分割檢視畫面中的兩個子項片段。範例 2 的主機片段則代管一個子項片段,該子項片段組成滑動檢視畫面的顯示片段。
有了這項設定,您可以視為每個主機皆具備可管理主機子項片段的相關聯 FragmentManager
。圖 2 顯示了相關說明,也呈現出 supportFragmentManager
、parentFragmentManager
和 childFragmentManager
之間的屬性對應方式。
應參照的 FragmentManager
屬性取決於呼叫網站在片段階層中的位置,以及您嘗試存取的片段管理員。
取得 FragmentManager
的參照後,即可用其操作向使用者顯示的片段。
子項片段
一般而言,應用程式是由應用程式專案中的單一或少量活動組成,每個活動都代表一組相關的螢幕畫面。該活動可能會提供放置頂層導覽的位置,並提供另一個位置來限定 ViewModel
物件和片段間其他檢視畫面狀態的範圍。片段代表應用程式中的個別目的地。
如果想一次顯示多個片段 (例如在分割檢視畫面或資訊主頁中),可使用由目的地片段及其子項片段管理員管理的子項片段。
子項片段的其他用途如下:
- 螢幕滑動:使用父項片段中的
ViewPager2
,管理一系列子項片段檢視畫面。 - 在一組相關的畫面中進行子導覽。
- Jetpack Navigation 使用子項片段做為個別到達網頁。活動代管單一父項
NavHostFragment
,並在使用者瀏覽應用程式時,填入不同的子項目的地片段。
使用 FragmentManager
FragmentManager
會管理片段返回堆疊。在執行階段,FragmentManager
可以執行返回堆疊作業來回應使用者互動,例如新增或移除片段。每組異動都會提交為一個單位,稱為 FragmentTransaction
。如要深入瞭解片段交易,請參閱「片段交易」指南。
當使用者輕觸裝置上的返回按鈕,或您呼叫 FragmentManager.popBackStack()
時,最頂端的片段交易會從堆疊中彈出。如果堆疊上沒有其他片段交易,且您未使用子項片段,則返回事件會向上傳遞至活動。如果您「的確」使用子項片段,請參閱「子項片段和同層片段的特別注意事項」。
您在交易中呼叫 addToBackStack()
時,交易可包含任意數量的作業,例如新增多個片段,或取代多個容器中的片段。
彈出返回堆疊時,所有這些作業都會撤銷為不可拆分的單一動作。不過,如果您在呼叫 popBackStack()
前提交了其他交易,且「並未」對交易使用 addToBackStack()
,則這些作業「不會」撤銷。因此,在單一 FragmentTransaction
中,請避免交錯處理會影響及不會影響返回堆疊的交易。
執行交易
如要在版面配置容器中顯示片段,請使用 FragmentManager
來建立 FragmentTransaction
。然後,您可以在交易中對容器執行 add()
或 replace()
作業。
舉例來說,簡易的 FragmentTransaction
可能如下所示:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("name") // Name can be null }
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack("name") // Name can be null .commit();
在這個範例中,ExampleFragment
會取代目前由 R.id.fragment_container
ID 識別的版面配置容器中的片段 (如有)。將片段的類別提供給 replace()
方法,讓 FragmentManager
使用 FragmentFactory
處理例項建立作業。詳情請參閱「為片段提供依附元件」一節。
setReorderingAllowed(true)
會最佳化交易中片段的狀態變更,因此動畫和轉場可以正常運作。如要進一步瞭解如何使用動畫和轉場效果,請參閱片段交易和使用動畫瀏覽各個片段。
呼叫 addToBackStack()
會將交易提交至返回堆疊。使用者稍後可輕觸返回按鈕來撤銷交易,恢復前一個片段。如果您在單一交易中新增或移除了多個片段,當系統彈出返回堆疊時,所有這些作業都會撤銷。您可以運用 addToBackStack()
呼叫所提供的選用名稱,使用 popBackStack()
彈回至該特定交易。
如果在執行移除片段的交易時不呼叫 addToBackStack()
,則交易提交時,系統會刪除已移除的片段,且使用者無法返回該片段。如果在移除片段時呼叫 addToBackStack()
,則該片段只會處於 STOPPED
狀態,且稍後使用者返回瀏覽時,片段的狀態為 RESUMED
。在這種情況下,該檢視畫面會遭到刪除。詳情請參閱「片段生命週期」一文。
找出現有片段
您可以使用 findFragmentById()
取得版面配置容器中目前片段的參照。使用 XML 時,請使用 findFragmentById()
查詢指定 ID 的片段;如要新增標記,請在 FragmentTransaction
中加入容器 ID。範例如下:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);
或者,您也可以使用 findFragmentByTag()
為片段指派不重複的標記,並取得參照。您可以在版面配置中定義的片段上使用 android:tag
XML 屬性來指派標記,或是在 FragmentTransaction
中的 add()
或 replace()
作業期間執行這項動作。
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container, "tag") setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentByTag("tag") as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null, "tag") .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");
子項片段和同層片段的特別注意事項
在任何特定時間,只有一個 FragmentManager
可控制片段返回堆疊。如果應用程式同時在螢幕畫面上顯示多個同層片段,或使用子項片段,則須指定一個 FragmentManager
來處理應用程式的主要導覽。
如要定義片段交易中的主要導覽片段,請在交易中呼叫 setPrimaryNavigationFragment()
方法,並傳入片段的例項,該例項的 childFragmentManager
應具有主要控制權。
請將導覽結構視為一系列層級,而活動是放在最外層,納入底下每層子項片段。每一層各有一個主要導覽片段。
發生返回事件時,最內層會控制導覽行為。一旦最內層沒有任何片段交易可彈回,控制權就會回到外面一層,並重複此過程,直到抵達活動為止。
同時顯示兩個以上的片段時,只有一個片段可以是主要導覽片段。如果將某個片段設為主要導覽片段,系統會移除對先前片段的標示。在上述示例中,如果將詳細資料片段設為主要導覽片段,系統會移除對主要片段的標示。
支援多個返回堆疊
在某些情況下,您的應用程式可能需要支援多個返回堆疊。最常見的例子是應用程式使用底部導覽列。您可以透過 FragmentManager
使用 saveBackStack()
和 restoreBackStack()
方法,支援多個返回堆疊。這些方法可用來儲存及還原返回堆疊,藉此切換不同返回堆疊。
saveBackStack()
的運作方式與呼叫 popBackStack()
相同,其中包含選用的 name
參數:彈出指定交易和堆疊上之後的所有交易。差別在於 saveBackStack()
會儲存已彈出交易中所有片段的狀態。
舉例來說,假設您先前使用 addToBackStack()
提交 FragmentTransaction
,將片段新增至返回堆疊,如以下範例所示:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("replacement") }
Java
supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) // setReorderingAllowed(true) and the optional string argument for // addToBackStack() are both required if you want to use saveBackStack() .setReorderingAllowed(true) .addToBackStack("replacement") .commit();
在這種情況下,您可以呼叫 saveBackStack()
來儲存這個片段交易和 ExampleFragment
的狀態:
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
您可以使用相同的名稱參數呼叫 restoreBackStack()
,還原所有已彈出的交易與所有已儲存的片段狀態:
Kotlin
supportFragmentManager.restoreBackStack("replacement")
Java
supportFragmentManager.restoreBackStack("replacement");
為片段提供依附元件
新增片段時,您可以手動將片段執行個體化,並將其新增至 FragmentTransaction
。
Kotlin
fragmentManager.commit { // Instantiate a new instance before adding val myFragment = ExampleFragment() add(R.id.fragment_view_container, myFragment) setReorderingAllowed(true) }
Java
// Instantiate a new instance before adding ExampleFragment myFragment = new ExampleFragment(); fragmentManager.beginTransaction() .add(R.id.fragment_view_container, myFragment) .setReorderingAllowed(true) .commit();
提交片段交易時,系統便會使用您建立的片段執行個體。但是,在設定變更期間,系統會先刪除活動和其所有片段,再使用最適用的 Android 資源重新建立這些項目。FragmentManager
會為您處理以下所有操作:重新建立片段的例項、將其附加至主機,並重新建立返回堆疊狀態。
根據預設,FragmentManager
會使用架構提供的 FragmentFactory
,對片段的新例項執行例項化作業。這個預設工廠會利用反射來查找並叫用片段的無引數建構函式。也就是說,您無法使用這個預設工廠為片段提供依附元件。這也表示在預設情況下,您在首次建立片段時使用的任何自訂建構函式,都「不會」在重新建立期間使用。
如要為片段提供依附元件,或是使用任何自訂建構函式,請改為建立自訂 FragmentFactory
子類別,然後覆寫 FragmentFactory.instantiate
。接著,您可以使用自訂工廠覆寫 FragmentManager
的預設工廠,並用來為片段進行例項化。
假設 DessertsFragment
負責顯示您家鄉的熱門甜點,且 DessertsFragment
在 DessertsRepository
類別上有依附元件,會提供向使用者顯示正確 UI 所需的資訊。
您可以定義 DessertsFragment
,在其建構函式中使用 DessertsRepository
例項。
Kotlin
class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() { ... }
Java
public class DessertsFragment extends Fragment { private DessertsRepository dessertsRepository; public DessertsFragment(DessertsRepository dessertsRepository) { super(); this.dessertsRepository = dessertsRepository; } // Getter omitted. ... }
實作簡單的 FragmentFactory
可能如下所示。
Kotlin
class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (loadFragmentClass(classLoader, className)) { DessertsFragment::class.java -> DessertsFragment(repository) else -> super.instantiate(classLoader, className) } }
Java
public class MyFragmentFactory extends FragmentFactory { private DessertsRepository repository; public MyFragmentFactory(DessertsRepository repository) { super(); this.repository = repository; } @NonNull @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className); if (fragmentClass == DessertsFragment.class) { return new DessertsFragment(repository); } else { return super.instantiate(classLoader, className); } } }
這個範例子類別 FragmentFactory
覆寫 instantiate()
方法,以便提供 DessertsFragment
的自訂片段建立邏輯。其他片段類別是透過 super.instantiate()
由 FragmentFactory
的預設行為進行處理。
然後,您可以在 FragmentManager
上設定屬性,將 MyFragmentFactory
指定為用來建構應用程式片段的工廠。您必須在活動的 super.onCreate()
之前設定這個屬性,以確保在重新建立片段時會使用 MyFragmentFactory
。
Kotlin
class MealActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance()) super.onCreate(savedInstanceState) } }
Java
public class MealActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { DessertsRepository repository = DessertsRepository.getInstance(); getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository)); super.onCreate(savedInstanceState); } }
在活動中設定 FragmentFactory
,會覆寫整個活動片段階層中的片段建立作業。換句話說,您新增的所有子項片段 childFragmentManager
都會使用在此設定的自訂片段工廠,除非這些片段在較低的層級遭到覆寫。
使用 FragmentFactory 進行測試
請在單一活動架構中,使用 FragmentScenario
類別單獨測試片段。由於您無法依賴活動的自訂 onCreate
邏輯,因此可以改將 FragmentFactory
做為引數傳入片段測試,如以下範例所示:
// Inside your test val dessertRepository = mock(DessertsRepository::class.java) launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment { // Test Fragment logic }
如要進一步瞭解這項測試程序和完整範例,請參閱「測試片段」。