導覽元件提供多種方法,讓您以程式化方式建立特定導覽元素並進行互動。
建立 NavHostFragment
您可以使用 NavHostFragment.create()
,透過程式化方式建立包含特定圖形資源的 NavHostFragment
,如以下範例所示:
Kotlin
val finalHost = NavHostFragment.create(R.navigation.example_graph) supportFragmentManager.beginTransaction() .replace(R.id.nav_host, finalHost) .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true" .commit()
Java
NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph); getSupportFragmentManager().beginTransaction() .replace(R.id.nav_host, finalHost) .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true" .commit();
請注意,setPrimaryNavigationFragment(finalHost)
可讓 NavHost
攔截對系統返回按鈕的按下動作。您也可以新增 app:defaultNavHost="true"
,在 NavHost
XML 中實作這一行為。如果您正在實作自訂返回按鈕行為,且不希望 NavHost
攔截返回按鈕按下動作,則可以將 null
傳遞到 setPrimaryNavigationFragment()
。
使用 NavBackStackEntry 參照到達網頁
從 Navigation 2.2.0 開始,您可以呼叫 NavController.getBackStackEntry()
,針對導覽堆疊上的任何到達網頁,取得對 NavBackStackEntry
的參照,將其傳遞至到達網頁 ID。如果返回堆疊含有指定到達網頁的多個執行個體,getBackStackEntry()
會傳回堆疊中最上層的執行個體。
傳回的 NavBackStackEntry
提供到達網頁層級的 Lifecycle
、ViewModelStore
和 SavedStateRegistry
。這些物件在返回堆疊上的到達網頁生命週期內有效。當關聯的到達網頁從返回堆疊中移除後,系統會刪除 Lifecycle
,然後不再儲存狀態,並清除所有 ViewModel
物件。
這些屬性用於提供 Lifecycle
,以及的 ViewModel
物件和類別的商店,不論您使用的到達網頁類型為何,這些物件和類別都可用於已儲存狀態。當使用不會自動具有關聯 Lifecycle
(例如自訂尺寸) 的到達網頁類型時,此功能非常有用。
舉例來說,您可以觀察 NavBackStackEntry
的 Lifecycle
,就像觀察片段或活動的 Lifecycle
一樣。此外,NavBackStackEntry
是 LifecycleOwner
,這表示您可以在觀察 LiveData
時使用它,或將其與其他生命週期感知元件配合使用,如以下範例所示:
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
當您呼叫 navigate()
時,生命週期狀態會自動更新。對於不在返回堆疊頂部的到達網頁,如果該到達網頁在 FloatingWindow
到達網頁 (如對話方塊到達網頁) 下方仍然可見,則其生命週期狀態會從 RESUMED
移至 STARTED
,如果不可見,則會移至 STOPPED
。
將結果傳回上一個到達網頁
在 Navigation 2.3 及以上版本中,NavBackStackEntry
提供對 SavedStateHandle
的存取權。SavedStateHandle
是鍵/值對應,可用來儲存及擷取資料。這些值在程序終止期間持續存在 (包括設定變更),且仍可透過相同物件存取。使用指定的 SavedStateHandle
,可以存取到達網頁資料並在到達網頁之間傳遞資料。當到達網頁從堆疊中移除後,可使用此方法取得從到達網頁傳回的資料。
如要將資料從到達網頁 B 傳回到達網頁 A,請先設定到達網頁 A 來監聽其 SavedStateHandle
的結果。方法是使用 getCurrentBackStackEntry()
API 擷取 NavBackStackEntry
,然後observe
由 SavedStateHandle
提供的 LiveData
。
Kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val navController = findNavController(); // We use a String here, but any type that can be put in a Bundle is supported navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe( viewLifecycleOwner) { result -> // Do something with the result. } }
Java
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { NavController navController = NavHostFragment.findNavController(this); // We use a String here, but any type that can be put in a Bundle is supported MutableLiveData<String> liveData = navController.getCurrentBackStackEntry() .getSavedStateHandle() .getLiveData("key"); liveData.observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(String s) { // Do something with the result. } }); }

在到達網頁 B 中,您必須使用 getPreviousBackStackEntry()
API,在到達網頁 A 的 SavedStateHandle
上set
結果。
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
如果您只想處理結果一次,則必須在 SavedStateHandle
上呼叫 remove()
以清除結果。如果您未移除結果,LiveData
會繼續將最後的結果傳回至新的 Observer
執行個體。
使用對話方塊到達網頁時的考量事項
當您navigate
至採用 NavHost
完整檢視畫面的的到達網頁 (例如 <fragment>
到達網頁) 時,先前的到達網頁會停止其生命週期,避免回呼 SavedStateHandle
提供的 LiveData
。
不過,前往對話方塊到達網頁時,先前的到達網頁也會顯示在畫面上,因此即使不是目前的到達網頁,也要 STARTED
。也就是說,在發生設定變更或程序終止和重新建立後,從 onViewCreated()
等生命週期方法發出的 getCurrentBackStackEntry()
呼叫,會傳回對話方塊到達網頁的 NavBackStackEntry
(因為對話方塊會在其他到達網頁上方還原)。因此,請將 getBackStackEntry()
與到達網頁 ID 搭配使用,確保一律使用正確的 NavBackStackEntry
。
這還意味著即使對話方塊到達網頁仍在畫面上,都會觸發在結果 LiveData
上設定的任何 Observer
。如果您只想在對話方塊到達網頁關閉且底層到達網頁成為當前到達網頁時查看結果,您可以觀察與 NavBackStackEntry
相關聯的 Lifecycle
,並僅當其變成 RESUMED
時擷取結果。
Kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val navController = findNavController(); // After a configuration change or process death, the currentBackStackEntry // points to the dialog destination, so you must use getBackStackEntry() // with the specific ID of your destination to ensure we always // get the right NavBackStackEntry val navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment) // Create our observer and add it to the NavBackStackEntry's lifecycle val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME && navBackStackEntry.savedStateHandle.contains("key")) { val result = navBackStackEntry.savedStateHandle.get<String>("key"); // Do something with the result } } navBackStackEntry.lifecycle.addObserver(observer) // As addObserver() does not automatically remove the observer, we // call removeObserver() manually when the view lifecycle is destroyed viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_DESTROY) { navBackStackEntry.lifecycle.removeObserver(observer) } }) }
Java
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); NavController navController = NavHostFragment.findNavController(this); // After a configuration change or process death, the currentBackStackEntry // points to the dialog destination, so you must use getBackStackEntry() // with the specific ID of your destination to ensure we always // get the right NavBackStackEntry final NavBackStackEntry navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment); // Create our observer and add it to the NavBackStackEntry's lifecycle final LifecycleEventObserver observer = new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event.equals(Lifecycle.Event.ON_RESUME) && navBackStackEntry.getSavedStateHandle().contains("key")) { String result = navBackStackEntry.getSavedStateHandle().get("key"); // Do something with the result } } }; navBackStackEntry.getLifecycle().addObserver(observer); // As addObserver() does not automatically remove the observer, we // call removeObserver() manually when the view lifecycle is destroyed getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event.equals(Lifecycle.Event.ON_DESTROY)) { navBackStackEntry.getLifecycle().removeObserver(observer) } } }); }
使用 ViewModel 在到達網頁之間分享 UI 相關資料
導覽返回堆疊會為每個個別到達網頁和包含個別到達網頁的父導覽圖儲存 NavBackStackEntry
。這可讓您擷取範圍限定在某個導覽圖的 NavBackStackEntry
。導覽圖範圍限定 NavBackStackEntry
可讓您建立範圍限定在某個導覽圖的 ViewModel
,以便在圖表到達網頁之間分享 UI 相關資料。透過這種方式建立的所有 ViewModel
物件,其活躍時間將持續至相關聯的 NavHost
及其 ViewModelStore
被清除,或直到導覽圖從返回堆疊中移除。
以下範例顯示如何擷取範圍限定至某個導覽圖的 ViewModel
:
Kotlin
val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph)
Java
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph); MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);
如果您使用 Navigation 2.2.0 或以下版本,則必須提供自己的工廠才能使用 ViewModel 的已儲存狀態,如以下範例所示:
Kotlin
val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) { SavedStateViewModelFactory(requireActivity().application, requireParentFragment()) }
Java
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph); ViewModelProvider viewModelProvider = new ViewModelProvider( backStackEntry.getViewModelStore(), new SavedStateViewModelFactory( requireActivity().getApplication(), requireParentFragment())); MyViewModel myViewModel = provider.get(myViewModel.getClass());
如要進一步瞭解 ViewModel
,請參閱 ViewModel 總覽。
修改加載的導覽圖
您可以在執行階段動態修改加載的導覽圖。
舉例來說,如果您把 BottomNavigationView
繫結至 NavGraph
,則 NavGraph
的預設到達網頁會在應用程式啟動時規定選取的分頁。但您可能需要覆寫此行為,例如當使用者偏好設定指定了要在應用程式啟動時載入的偏好分頁時。或者,您的應用程式可能需要根據過往的使用者行為來變更起始分頁。以動態方式指定 NavGraph
的預設到達網頁,即可支援這些情境。
考慮這個 NavGraph
:
<?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/home"> <fragment android:id="@+id/home" android:name="com.example.android.navigation.HomeFragment" android:label="fragment_home" tools:layout="@layout/fragment_home" /> <fragment android:id="@+id/location" android:name="com.example.android.navigation.LocationFragment" android:label="fragment_location" tools:layout="@layout/fragment_location" /> <fragment android:id="@+id/shop" android:name="com.example.android.navigation.ShopFragment" android:label="fragment_shop" tools:layout="@layout/fragment_shop" /> <fragment android:id="@+id/settings" android:name="com.example.android.navigation.SettingsFragment" android:label="fragment_settings" tools:layout="@layout/fragment_settings" /> </navigation>
載入這張圖表時,app:startDestination
屬性會指定要顯示的 HomeFragment
。如要動態覆寫起始到達網頁,請執行下列步驟:
- 首先,手動加載
NavGraph
。 - 覆寫起始到達網頁。
- 最後,手動將圖表附加到
NavController
。
Kotlin
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController val navGraph = navController.navInflater.inflate(R.navigation.bottom_nav_graph) navGraph.startDestination = R.id.shop navController.graph = navGraph binding.bottomNavView.setupWithNavController(navController)
Java
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() .findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.bottom_nav_graph); navGraph.setStartDestination(R.id.shop); navController.setGraph(navGraph); NavigationUI.setupWithNavController(binding.bottomNavView, navController);
現在,您的應用程式啟動時會顯示 ShopFragment
,而不是 HomeFragment
。
使用深層連結時,NavController
會自動為深層連結到達網頁建構返回堆疊。如果使用者導覽至深層連結,然後一直向後導覽,則會在某個時間到達起始到達網頁。使用上一個範例中所述的技巧來覆寫起始到達網頁,可確保將正確的起始到達網頁新增至建構的返回堆疊。
請注意,這項技巧也可讓您視需要覆寫 NavGraph
的其他部分。對圖表所做的所有修改都必須在呼叫 setGraph()
之前完成,以確保在處理深層連結、還原狀態和移至圖表起始到達網頁時使用正確的結構。