Компонент «Навигация» предоставляет способы программного создания и взаимодействия с определенными элементами навигации.
Создайте NavHostFragment.
Вы можете использовать NavHostFragment.create()
для программного создания NavHostFragment
с определенным ресурсом графа, как показано в примере ниже:
Котлин
val finalHost = NavHostFragment.create(R.navigation.example_graph) supportFragmentManager.beginTransaction() .replace(R.id.nav_host, finalHost) .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true" .commit()
Ява
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
перехватывать нажатия кнопки «Назад». Вы также можете реализовать это поведение в своем XML-коде NavHost
добавив app:defaultNavHost="true"
. Если вы реализуете собственное поведение кнопки «Назад» и не хотите, чтобы NavHost
перехватывал нажатия кнопки «Назад», вы можете передать null
в setPrimaryNavigationFragment()
.
Ссылка на пункт назначения с помощью NavBackStackEntry
Начиная с версии Navigation 2.2.0, вы можете получить ссылку на NavBackStackEntry
для любого пункта назначения в стеке навигации, вызвав NavController.getBackStackEntry()
, передав ему идентификатор пункта назначения. Если задний стек содержит более одного экземпляра указанного пункта назначения, getBackStackEntry()
возвращает самый верхний экземпляр из стека.
Возвращенный NavBackStackEntry
предоставляет Lifecycle
, ViewModelStore
и SavedStateRegistry
на уровне назначения. Эти объекты действительны в течение всего времени существования пункта назначения в заднем стеке. Когда связанный пункт назначения извлекается из заднего стека, Lifecycle
уничтожается, состояние больше не сохраняется, а все объекты ViewModel
очищаются.
Эти свойства предоставляют вам Lifecycle
и хранилище для объектов и классов ViewModel
, которые работают с сохраненным состоянием независимо от того, какой тип места назначения вы используете. Это особенно полезно при работе с типами мест назначения, которые автоматически не имеют связанного Lifecycle
, например с настраиваемыми местами назначения.
Например, вы можете наблюдать за Lifecycle
NavBackStackEntry
так же, как если бы вы наблюдали за Lifecycle
фрагмента или действия. Кроме того, NavBackStackEntry
является LifecycleOwner
, что означает, что вы можете использовать его при наблюдении LiveData
или с другими компонентами, поддерживающими жизненный цикл, как показано в следующем примере:
Котлин
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Ява
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
Состояние жизненного цикла автоматически обновляется всякий раз, когда вы вызываете navigate()
. Состояния жизненного цикла для целевых объектов, которые не находятся в верхней части заднего стека, переходят из RESUMED
в STARTED
, если места назначения все еще видны под местом назначения FloatingWindow
, например, в месте назначения диалога, или в состояние STOPPED
в противном случае.
Возврат результата в предыдущий пункт назначения
В навигации 2.3 и более поздних версиях NavBackStackEntry
предоставляет доступ к SavedStateHandle
. SavedStateHandle
— это карта значений ключа, которую можно использовать для хранения и извлечения данных. Эти значения сохраняются даже после смерти процесса, включая изменения конфигурации, и остаются доступными через тот же объект. Используя данный SavedStateHandle
, вы можете получать доступ к данным и передавать их между пунктами назначения. Это особенно полезно в качестве механизма возврата данных из места назначения после их извлечения из стека.
Чтобы передать данные обратно в пункт назначения A из пункта назначения B, сначала настройте пункт назначения A для прослушивания результата в его SavedStateHandle
. Для этого извлеките NavBackStackEntry
с помощью API getCurrentBackStackEntry()
, а затем observe
LiveData
, предоставленную SavedStateHandle
.
Котлин
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. } }
Ява
@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 вы должны set
результат в SavedStateHandle
пункта назначения A с помощью API getPreviousBackStackEntry()
.
Котлин
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Ява
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
Если вы хотите обработать результат только один раз, вам необходимо вызвать remove()
для SavedStateHandle
, чтобы очистить результат. Если вы не удалите результат, LiveData
продолжит возвращать последний результат всем новым экземплярам Observer
.
Рекомендации по использованию пунктов назначения диалога
Когда вы navigate
к месту назначения, которое обеспечивает полное представление NavHost
(например, к месту назначения <fragment>
), жизненный цикл предыдущего пункта назначения останавливается, что предотвращает любые обратные вызовы к LiveData
, предоставленным SavedStateHandle
.
Однако при переходе к пункту назначения диалога предыдущий пункт назначения также виден на экране и, следовательно, также STARTED
несмотря на то, что он не является текущим пунктом назначения. Это означает, что вызовы getCurrentBackStackEntry()
из методов жизненного цикла, таких как onViewCreated()
вернут NavBackStackEntry
пункта назначения диалога после изменения конфигурации или смерти и восстановления процесса (поскольку диалог восстанавливается выше другого пункта назначения). Поэтому вам следует использовать getBackStackEntry()
с идентификатором пункта назначения, чтобы гарантировать, что вы всегда используете правильный NavBackStackEntry
.
Это также означает, что любой Observer
, который вы установите для результата LiveData
будет запущен, даже если пункты назначения диалога все еще находятся на экране. Если вы хотите проверить результат только тогда, когда пункт назначения диалога закрыт и базовый пункт назначения становится текущим пунктом назначения, вы можете наблюдать Lifecycle
, связанный с NavBackStackEntry
, и получать результат только тогда, когда он становится RESUMED
.
Котлин
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) } }) }
Ява
@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.
В обратном стеке навигации хранится NavBackStackEntry
не только для каждого отдельного пункта назначения, но также для каждого родительского графа навигации, содержащего отдельный пункт назначения. Это позволяет вам получить NavBackStackEntry
, область действия которого ограничена графом навигации. NavBackStackEntry
в области навигационного графа предоставляет способ создания ViewModel
, область действия которого ограничена навигационным графом, что позволяет обмениваться данными, связанными с пользовательским интерфейсом, между пунктами назначения графа. Любые объекты ViewModel
, созданные таким образом, живут до тех пор, пока связанный NavHost
и его ViewModelStore
не будут очищены или пока граф навигации не будет извлечен из заднего стека.
В следующем примере показано, как получить ViewModel
, ограниченную графом навигации:
Котлин
val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph)
Ява
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph); MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);
Если вы используете Navigation 2.2.0 или более раннюю версию, вам необходимо предоставить собственную фабрику для использования Saved State с ViewModels , как показано в следующем примере:
Котлин
val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) { SavedStateViewModelFactory(requireActivity().application, requireParentFragment()) }
Ява
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
.
Котлин
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)
Ява
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);
Теперь при запуске вашего приложения вместо HomeFragment
отображается ShopFragment
.
При использовании глубоких ссылок NavController
автоматически создает обратный стек для места назначения глубоких ссылок. Если пользователь перейдет по глубокой ссылке, а затем вернется назад, в какой-то момент он достигнет начальной точки назначения. Переопределение начального пункта назначения с использованием метода из предыдущего примера гарантирует, что правильный начальный пункт назначения будет добавлен в построенный обратный стек.
Обратите внимание, что этот метод также позволяет при необходимости переопределять другие аспекты NavGraph
. Все изменения в графе должны быть выполнены до вызова setGraph()
чтобы гарантировать использование правильной структуры при обработке глубоких ссылок, восстановлении состояния и перемещении к начальному пункту назначения вашего графа.