導覽元件提供多種方法,讓您以程式化方式建立特定導覽元素並進行互動。
建立 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
物件。
這些屬性可提供 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()
之前完成,以確保在處理深層連結、還原狀態和移至圖表起始到達網頁時使用正確的結構。