Komponent Nawigacja umożliwia automatyczne tworzenie określonych elementów nawigacyjnych i korzystanie z nich.
Tworzenie NavHostFragment
Za pomocą narzędzia NavHostFragment.create()
możesz automatycznie utworzyć element NavHostFragment
z konkretnym zasobem wykresu, jak pokazano w tym przykładzie:
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();
Pamiętaj, że setPrimaryNavigationFragment(finalHost)
umożliwia urządzeniu NavHost
przechwytywanie naciśnięć systemowego przycisku Wstecz. Możesz też zaimplementować to działanie w pliku XML NavHost
, dodając app:defaultNavHost="true"
. Jeśli implementujesz niestandardowe działanie przycisku Wstecz i nie chcesz, aby system NavHost
przechwytywał naciśnięcia przycisku Wstecz, możesz przekazać parametr null
do parametru setPrimaryNavigationFragment()
.
Odwołanie do miejsca docelowego za pomocą NavBackStackEntry
Od Nawigacji 2.2.0 możesz uzyskać odwołanie do obiektu NavBackStackEntry
dla dowolnego miejsca docelowego w stosie nawigacyjnym, wywołując NavController.getBackStackEntry()
i przekazując mu identyfikator miejsca docelowego. Jeśli stos wsteczny zawiera więcej niż jedną instancję w podanym miejscu docelowym, getBackStackEntry()
zwraca instancję najwyższego poziomu ze stosu.
Zwracany NavBackStackEntry
zawiera atrybuty Lifecycle
, ViewModelStore
i SavedStateRegistry
na poziomie miejsca docelowego. Obiekty te są ważne przez cały okres istnienia miejsca docelowego na stosie wstecznym. Po usunięciu powiązanego miejsca docelowego ze stosu wstecznego Lifecycle
zostaje zniszczone, stan nie jest już zapisany, a wszystkie obiekty ViewModel
zostają wyczyszczone.
Te właściwości udostępniają Lifecycle
oraz magazyn obiektów i klas ViewModel
, które działają ze zapisanym stanem niezależnie od używanego miejsca docelowego. Jest to szczególnie przydatne w przypadku typów miejsc docelowych, które nie mają automatycznie powiązanego elementu Lifecycle
, takich jak niestandardowe miejsca docelowe.
Możesz na przykład obserwować Lifecycle
elementu NavBackStackEntry
tak samo jak Lifecycle
fragmentu lub aktywności. Dodatkowo NavBackStackEntry
to właściwość LifecycleOwner
, co oznacza, że możesz jej używać podczas obserwowania LiveData
lub innych komponentów uwzględniających cykl życia, jak pokazano w tym przykładzie:
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
Stan cyklu życia jest automatycznie aktualizowany za każdym razem, gdy zadzwonisz do: navigate()
.
Stany cyklu życia w przypadku miejsc docelowych, które nie znajdują się na górze stosu wstecznego, są przenoszone z RESUMED
do STARTED
, jeśli są one nadal widoczne pod miejscem docelowym FloatingWindow
, takim jak miejsce docelowe okna, lub do STOPPED
w innym przypadku.
Zwracanie wyniku do poprzedniego miejsca docelowego
W Nawigacji w wersji 2.3 i nowszych NavBackStackEntry
umożliwia dostęp do SavedStateHandle
.
SavedStateHandle
to mapa klucz-wartość, która może służyć do przechowywania i pobierania danych. Te wartości pozostają dostępne po zakończeniu procesu (w tym po zmianie konfiguracji) i pozostają dostępne w tym samym obiekcie. Używając podanego SavedStateHandle
, możesz uzyskiwać dostęp do danych i przekazywać je między miejscami docelowymi.
Jest to szczególnie przydatne jako mechanizm odzyskiwania danych z miejsca docelowego po jego usunięciu ze stosu.
Aby przekazywać dane z powrotem do miejsca docelowego A z miejsca docelowego B, najpierw skonfiguruj miejsce docelowe A, by nasłuchiwać wyniku na urządzeniu SavedStateHandle
.
Aby to zrobić, pobierz NavBackStackEntry
za pomocą interfejsu API getCurrentBackStackEntry()
, a potem observe
LiveData
dostarczany przez SavedStateHandle
.
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. } }); }
W miejscu docelowym B musisz set
wynik w SavedStateHandle
miejsca docelowego A za pomocą interfejsu API getPreviousBackStackEntry()
.
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
Jeśli chcesz zmodyfikować wynik tylko raz, musisz wywołać remove()
w metodzie SavedStateHandle
, aby go wyczyścić. Jeśli nie usuniesz wyniku, LiveData
będzie nadal zwracać ostatni wynik w nowych instancjach typu Observer
.
O czym warto pamiętać w przypadku korzystania z miejsc docelowych okien
Gdy wykonasz żądanie navigate
do miejsca docelowego, które ma pełny widok obiektu NavHost
(np. <fragment>
), cykl życia poprzedniego miejsca docelowego zostanie zatrzymany, co uniemożliwi wywołanie zwrotne metody LiveData
dostarczanej przez SavedStateHandle
.
Jednak w oknie docelowym poprzednie miejsce docelowe jest też widoczne na ekranie i dlatego jest widoczne również jako STARTED
, mimo że nie jest to aktualne miejsce docelowe. Oznacza to, że po zmianie konfiguracji lub zakończeniu procesu śmierci i odtwarzania wywołania getCurrentBackStackEntry()
z metod cyklu życia, takich jak onViewCreated()
, zwracają NavBackStackEntry
okna docelowego (ponieważ okno zostanie przywrócone nad drugim miejscem docelowym). Aby używać prawidłowego atrybutu NavBackStackEntry
, użyj atrybutu getBackStackEntry()
z identyfikatorem miejsca docelowego.
Oznacza to również, że każdy element Observer
ustawiony w wyniku LiveData
będzie wywoływany nawet wtedy, gdy miejsca docelowe w oknach są nadal widoczne na ekranie. Jeśli chcesz sprawdzić wynik tylko wtedy, gdy okno docelowe jest zamknięte, a docelowe miejsce docelowe stanie się bieżącym miejscem docelowym, możesz obserwować właściwość Lifecycle
powiązaną z elementem NavBackStackEntry
i pobierać wynik dopiero wtedy, gdy zmieni się on na 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) } } }); }
Udostępniaj dane związane z interfejsem użytkownika między miejscami docelowymi za pomocą modelu ViewModel
Stos nawigacji wstecz przechowuje NavBackStackEntry
nie tylko dla każdego miejsca docelowego, ale także dla każdego nadrzędnego wykresu nawigacji, który zawiera dane miejsce docelowe. Dzięki temu możesz pobrać element NavBackStackEntry
ograniczony do wykresu nawigacyjnego. Interfejs NavBackStackEntry
ograniczony do wykresu nawigacji umożliwia utworzenie elementu ViewModel
obejmującego wykres nawigacyjny, co umożliwia udostępnianie danych związanych z interfejsem między miejscami docelowymi wykresu. Wszystkie utworzone w ten sposób obiekty ViewModel
będą działać do czasu wyczyszczenia powiązanych z nimi elementów NavHost
i ViewModelStore
lub do momentu usunięcia wykresu nawigacyjnego ze stosu.
Z przykładu poniżej dowiesz się, jak pobrać element ViewModel
ograniczony do wykresu nawigacyjnego:
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);
Jeśli korzystasz z Nawigacji w wersji 2.2.0 lub starszej, musisz podać własną fabrykę, która będzie używać zapisanych stanów z obiektami ViewModels, jak w tym przykładzie:
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());
Więcej informacji o modelu ViewModel
znajdziesz w artykule Omówienie modelu ViewModel.
Modyfikowanie rozszerzonych wykresów nawigacyjnych
Rozwinięty wykres nawigacyjny można modyfikować dynamicznie w czasie działania.
Jeśli na przykład BottomNavigationView
jest powiązany z kartą NavGraph
, wybrana karta podczas uruchamiania aplikacji będzie dyktowana przez domyślne miejsce docelowe karty NavGraph
. Może być jednak konieczne zastąpienie tego działania, np. gdy ustawienie użytkownika określa preferowaną kartę do wczytywania podczas uruchamiania aplikacji. Aplikacja może też wymagać zmiany karty początkowej na podstawie wcześniejszych działań użytkowników. Możesz w tym celu dynamicznie określić domyślne miejsce docelowe obiektu NavGraph
.
Rozważ ten 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>
Podczas wczytywania wykresu atrybut app:startDestination
określa, że ma być wyświetlana wartość HomeFragment
. Aby dynamicznie zastąpić miejsce docelowe początkowego, wykonaj te czynności:
- Najpierw ręcznie powiększ kolumnę
NavGraph
. - Zastąp początkową lokalizację docelową.
- Na koniec ręcznie dołącz wykres do elementu
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);
Teraz po uruchomieniu aplikacji wyświetlany jest ShopFragment
zamiast HomeFragment
.
Gdy używasz precyzyjnych linków, NavController
automatycznie tworzy stos wsteczny dla miejsca docelowego precyzyjnego linku. Jeśli użytkownik przejdzie do precyzyjnego linku, a potem przejdzie wstecz, w pewnym momencie dotrze do miejsca docelowego. Zastąpienie początkowego miejsca docelowego za pomocą metody z poprzedniego przykładu zapewni, że do utworzonego stosu wstecznego zostanie dodane prawidłowe miejsce docelowe.
Pamiętaj, że ta metoda umożliwia również zastępowanie innych aspektów atrybutu NavGraph
zgodnie z wymaganiami. Wszelkie zmiany na wykresie należy wprowadzać przed wywołaniem funkcji setGraph()
, aby mieć pewność, że podczas obsługi precyzyjnych linków, przywracania stanu i przejścia do początkowej lokalizacji wykresu wykres będzie mieć prawidłową strukturę.