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<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 相關資料
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
:
<?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()
「之前」完成,確保在處理深層連結、還原狀態及移至圖表的起始目的地時,能使用正確結構。