导航组件提供以编程方式创建和与某些导航元素交互的方法。
创建 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
拦截系统后退按钮的按下操作。您也可以通过在 NavHost
XML 中添加 app:defaultNavHost="true"
来实现此行为。如果您正在实现 自定义后退按钮行为 并且不希望 NavHost
拦截后退按钮的按下操作,则可以将 null
传递给 setPrimaryNavigationFragment()
。
使用 NavBackStackEntry 引用目标
从 Navigation 2.2.0 开始,您可以通过调用 NavController.getBackStackEntry()
来获取对导航堆栈中任何目标的 NavBackStackEntry
的引用,并将其传递给目标 ID。如果后退堆栈包含指定目标的多个实例,getBackStackEntry()
将返回堆栈中最顶层的实例。
返回的 NavBackStackEntry
在目标级别提供一个 Lifecycle
、一个 ViewModelStore
和一个 SavedStateRegistry
。这些对象在目标在后退堆栈上的生存期内有效。当关联的目标从后退堆栈中弹出时,Lifecycle
将被销毁,状态不再保存,任何 ViewModel
对象都将被清除。
这些属性为您提供了一个Lifecycle
和一个存储ViewModel
对象和类的存储空间,这些对象和类与保存状态一起使用,无论您使用哪种类型的目标。这在处理没有自动关联的Lifecycle
的目标类型时特别有用,例如自定义目标。
例如,您可以像观察片段或活动的Lifecycle
一样观察NavBackStackEntry
的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()
时,生命周期状态会自动更新。不在回退栈顶部的目标的生命周期状态会从RESUMED
变为STARTED
,如果目标仍然在FloatingWindow
目标(例如对话框目标)下可见,否则会变为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
。范围与导航图相同的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 或更早版本,则需要提供自己的工厂来使用ViewModels 的保存状态,如以下示例所示
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 概述。
修改已膨胀的导航图
您可以在运行时动态修改已膨胀的导航图。
例如,如果您有一个绑定到NavGraph
的BottomNavigationView
,则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()
之前完成,以确保在处理深层链接、恢复状态和移动到图的起始目标时使用正确的结构。