Navigation 元件提供多種方法,讓您以程式輔助方式建立特定導覽元素並進行互動。
建立 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()
並傳遞目的地 ID,為導覽堆疊上的任何目的地取得 NavBackStackEntry
的參照。如果返回堆疊含有指定目的地的多個例項,getBackStackEntry()
會傳回堆疊中最頂層的例項。
傳回的 NavBackStackEntry
提供目的地層級的 Lifecycle
、ViewModelStore
和 SavedStateRegistry
。這些物件的有效期間與返回堆疊上的目的地生命週期相同。相關聯的目的地從返回堆疊中消除後,系統會刪除 Lifecycle
,不再儲存狀態,並清除所有 ViewModel
物件。
這些屬性會提供 ViewModel
和 Lifecycle
物件與類別的儲存庫,不論您使用何種目的地類型,這些物件和類別都可與儲存的狀態搭配運作。與不會自動設定相關 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>("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> liveData = navController.getCurrentBackStackEntry() .getSavedStateHandle() .getLiveData("key"); liveData.observe(getViewLifecycleOwner(), new Observer >() { @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
完整檢視畫面的目的地 (例如
目的地) 時,先前的目的地會將其生命週期設為停止,避免對 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>("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 相關資料
Navigation 返回堆疊不僅會為個別目的地儲存 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
:
version="1.0" encoding="utf-8"?>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"> android:id="@+id/home" android:name="com.example.android.navigation.HomeFragment" android:label="fragment_home" tools:layout="@layout/fragment_home" /> android:id="@+id/location" android:name="com.example.android.navigation.LocationFragment" android:label="fragment_location" tools:layout="@layout/fragment_location" /> android:id="@+id/shop" android:name="com.example.android.navigation.ShopFragment" android:label="fragment_shop" tools:layout="@layout/fragment_shop" /> android:id="@+id/settings" android:name="com.example.android.navigation.SettingsFragment" android:label="fragment_settings" tools:layout="@layout/fragment_settings" />
載入這張圖表時,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()
「之前」完成,確保在處理深層連結、還原狀態及移至圖表的起始目的地時,能使用正確結構。