以编程方式与导航组件交互

导航组件提供以编程方式创建和与某些导航元素交互的方法。

创建 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()

从 Navigation 2.2.0 开始,您可以通过调用 NavController.getBackStackEntry() 来获取对导航堆栈中任何目标的 NavBackStackEntry 的引用,并将其传递给目标 ID。如果后退堆栈包含指定目标的多个实例,getBackStackEntry() 将返回堆栈中最顶层的实例。

返回的 NavBackStackEntry 在目标级别提供一个 Lifecycle、一个 ViewModelStore 和一个 SavedStateRegistry。这些对象在目标在后退堆栈上的生存期内有效。当关联的目标从后退堆栈中弹出时,Lifecycle 将被销毁,状态不再保存,任何 ViewModel 对象都将被清除。

这些属性为您提供了一个Lifecycle和一个存储ViewModel对象和类的存储空间,这些对象和类与保存状态一起使用,无论您使用哪种类型的目标。这在处理没有自动关联的Lifecycle的目标类型时特别有用,例如自定义目标。

例如,您可以像观察片段或活动的Lifecycle一样观察NavBackStackEntryLifecycle。此外,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,然后observeSavedStateHandle提供的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 的SavedStateHandleset结果。

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)
            }
        }
    });
}

导航回退栈不仅为每个单独的目标存储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 概述

修改已膨胀的导航图

您可以在运行时动态修改已膨胀的导航图。

例如,如果您有一个绑定到NavGraphBottomNavigationView,则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。要动态覆盖起始目标,请执行以下操作

  1. 首先,手动膨胀NavGraph
  2. 覆盖起始目标。
  3. 最后,手动将图附加到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()之前完成,以确保在处理深层链接、恢复状态和移动到图的起始目标时使用正确的结构。