导航组件提供以程序化方式创建某些导航元素并与之交互的方法。
创建 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
的目的地类型(例如自定义目的地)时,此方法尤其有用。
例如,您可以像观察 Fragment 或 Activity 的 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
,然后使用 SavedStateHandle
提供的 LiveData
进行 observe
操作。
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 在目的地之间共享界面相关数据
Navigation 返回堆栈不仅针对每个单个目的地存储 NavBackStackEntry
,还会针对包含单个目的地的父级导航图存储。这样,您就可以检索范围限定于导航图的 NavBackStackEntry
。借助范围限定于导航图的 NavBackStackEntry
,您可以创建范围限定于导航图的 ViewModel
,从而使您能够在该图表的目的地间共享与界面相关的数据。以这种方式创建的任何 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 概览。
修改膨胀的导航图
您可以在运行时动态修改膨胀的导航图。
例如,如果您有绑定到 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()
之前完成,以确保在处理深层链接、恢复状态和移动到图表的起始目的地时使用正确的结构。