Komponent Nawigacja to biblioteka, która może zarządzać złożoną nawigacją, animacją przejścia, precyzyjnymi linkami i argumentem sprawdzonym w czasie skompilowania wiadomości między ekranami w aplikacji.
Ten dokument służy jako ogólny przewodnik do przenoszenia istniejącej aplikacji w celu korzystania z komponentu Nawigacja.
Ogólnie migracja obejmuje te kroki:
Przenieś logikę interfejsu specyficzną dla ekranu z aktywności – przenieś logikę interfejsu aplikacji poza działania, dbając o to, aby każda aktywność odpowiadała tylko logice komponentów globalnego interfejsu nawigacji, np.
Toolbar
, a jednocześnie przydzielono implementację każdego ekranu do fragmentu lub niestandardowego miejsca docelowego.Integracja komponentu Nawigacja – w przypadku każdego działania utwórz wykres nawigacyjny zawierający co najmniej 1 fragment, którym zarządza to działanie. Zastąp transakcje fragmentami operacjami komponentu Nawigacja.
Dodawanie miejsc docelowych aktywności – zastępuj połączenia w
startActivity()
działaniami, które korzystają z miejsc docelowych aktywności.Łączenie działań – połącz wykresy nawigacji, jeśli wiele działań ma ten sam układ.
Wymagania wstępne
W tym przewodniku zakładamy, że Twoja aplikacja została już przeniesiona do bibliotek AndroidaX. Zanim przejdziesz dalej, przenieś projekt do AndroidaX, chyba że już masz to za sobą.
Przenieś logikę interfejsu specyficzną dla ekranu z aktywności
Aktywności to komponenty na poziomie systemu, które ułatwiają interakcję graficzną między aplikacją a Androidem. Aktywności są rejestrowane w pliku manifestu aplikacji, dzięki czemu Android wie, które działania są dostępne do uruchomienia. Klasa aktywności umożliwia też reagowanie aplikacji na zmiany w Androidzie, np. gdy interfejs aplikacji pojawia się na pierwszym planie lub z niego wychodzi, obraca się itd. Aktywność może też służyć jako miejsce do udostępniania stanu między ekranami.
W kontekście aplikacji działania powinny służyć jako host do nawigacji, a także logicznie i zgodnie z wiedzą, jak przechodzić między ekranami, przekazywać dane itd. Do zarządzania szczegółami interfejsu lepiej jest jednak pozostawić jego mniejszą część, której można używać wielokrotnie. Zalecana implementacja tego wzorca to fragmenty. Więcej informacji o zaletach korzystania z fragmentów znajdziesz w artykule Pojedyncza aktywność: dlaczego, kiedy i jak. Nawigacja obsługuje fragmenty przez zależność navigation-fragment. Nawigacja obsługuje też niestandardowe typy miejsc docelowych.
Jeśli aplikacja nie używa fragmentów, najpierw musisz przenieść każdy z ekranów aplikacji, aby używał fragmentów. Na tym etapie nie usuwasz aktywności. Tworzysz fragment, który przedstawia ekran i rozdziela logikę interfejsu przez odpowiedzialność.
Przedstawiamy fragmenty
Aby pokazać proces wprowadzania fragmentów, zacznijmy od przykładu aplikacji, która składa się z 2 ekranów: ekranu z listą produktów i ekranem szczegółów produktu. Kliknięcie usługi na ekranie listy powoduje wyświetlenie ekranu szczegółów, na którym można znaleźć więcej informacji o produkcie.
W tym przykładzie ekrany listy i szczegółów to obecnie osobne działania.
Utwórz nowy układ do hostowania interfejsu
Aby wprowadzić fragment, zacznij od utworzenia nowego pliku układu dla działania, który będzie hostować fragment. Zastępuje on bieżący układ widoku treści aktywności.
Aby utworzyć prosty widok, możesz użyć właściwości FrameLayout
, jak w tym przykładzie product_list_host
:
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent" />
Atrybut id
odnosi się do sekcji treści, do której później dodamy fragment.
Następnie w funkcji onCreate()
aktywności zmodyfikuj odwołanie do pliku układu w funkcji onCreate Twojej aktywności, aby wskazać ten nowy plik układu:
Kotlin
class ProductListActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { ... // Replace setContentView(R.layout.product_list) with the line below setContentView(R.layout.product_list_host) ... } }
Java
public class ProductListActivity extends AppCompatActivity { ... @Override public void onCreate(@Nullable Bundle savedInstanceState) { ... // Replace setContentView(R.layout.product_list); with the line below setContentView(R.layout.product_list_host); ... } }
Bieżący układ (w tym przykładzie product_list
) jest używany jako widok główny tworzonego fragmentu.
Utwórz fragment
Utwórz nowy fragment, aby zarządzać interfejsem ekranu. Warto zachować spójność z nazwą hosta aktywności. W poniższym fragmencie kodu użyto parametru ProductListFragment
, na przykład:
Kotlin
class ProductListFragment : Fragment() { // Leave empty for now. }
Java
public class ProductListFragment extends Fragment { // Leave empty for now. }
Przenieś logikę aktywności do fragmentu
Po określeniu definicji fragmentu następnym krokiem jest przeniesienie logiki interfejsu dla tego ekranu z aktywności do tego nowego fragmentu. Jeśli korzystasz z architektury opartej na aktywności, prawdopodobnie funkcja onCreate()
aktywności działa w dużym stopniu logiki tworzenia widoków.
Oto przykładowy ekran z elementami interfejsu pokazującymi aktywność, który trzeba przenieść:
Kotlin
class ProductListActivity : AppCompatActivity() { // Views and/or ViewDataBinding references, Adapters... private lateinit var productAdapter: ProductAdapter private lateinit var binding: ProductListActivityBinding ... // ViewModels, System Services, other Dependencies... private val viewModel: ProductListViewModel by viewModels() ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity) // Post view initialization logic // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } ... }
Java
public class ProductListActivity extends AppCompatActivity { // Views and/or ViewDataBinding references, adapters... private ProductAdapter productAdapter; private ProductListActivityBinding binding; ... // ViewModels, system services, other dependencies... private ProductListViewModel viewModel; ... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity); // Post view initialization logic // Connect adapters productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this).get(ProductListViewModel.java); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(v -> { ... }); // Subscribe to state viewModel.getProducts().observe(this, myProducts -> ... ); // ...and so on }
Twoja aktywność może też wpływać na to, kiedy i w jaki sposób użytkownik przechodzi do następnego ekranu, jak widać w tym przykładzie:
Kotlin
// Provided to ProductAdapter in ProductListActivity snippet. private val productClickCallback = ProductClickCallback { product -> show(product) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) }
Java
// Provided to ProductAdapter in ProductListActivity snippet. private ProductClickCallback productClickCallback = this::show; private void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); }
Wewnątrz fragmentu rozdzielasz te działania między onCreateView()
i onViewCreated()
, pozostawiając w działaniu tylko logikę nawigacyjną:
Kotlin
class ProductListFragment : Fragment() { private lateinit var binding: ProductListFragmentBinding private val viewModel: ProductListViewModel by viewModels() // View initialization logic override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = DataBindingUtil.inflate( inflater, R.layout.product_list, container, false ) return binding.root } // Post view initialization logic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } // Provided to ProductAdapter private val productClickCallback = ProductClickCallback { product -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { (requireActivity() as ProductListActivity).show(product) } } ... }
Java
public class ProductListFragment extends Fragment { private ProductAdapter productAdapter; private ProductListFragmentBinding binding; // View initialization logic @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate( inflater, R.layout.product_list_fragment, container, false); return binding.getRoot(); } // Post view initialization logic @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // Connect adapters binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this) .get(ProductListViewModel.class); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(...) // Subscribe to state viewModel.getProducts().observe(this, myProducts -> { ... }); // ...and so on // Provided to ProductAdapter private ProductClickCallback productClickCallback = new ProductClickCallback() { @Override public void onClick(Product product) { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { ((ProductListActivity) requireActivity()).show(product); } } }; ... }
Zwróć uwagę, że w usłudze ProductListFragment
nie ma wywołania metody setContentView()
umożliwiającej powiększanie i łączenie układu. We fragmencie onCreateView()
inicjuje widok główny. onCreateView()
wykorzystuje instancję LayoutInflater
, która może służyć do rozszerzania widoku głównego na podstawie pliku zasobów układu. W tym przykładzie wykorzystywany jest istniejący układ product_list
, który był używany przez aktywność, ponieważ nie trzeba wprowadzać żadnych zmian w układzie.
Jeśli funkcje onStart()
, onResume()
, onPause()
lub onStop()
powiązane z nawigacją, które nie są związane z nawigacją, działają w interfejsie, możesz je przenieść do odpowiednich funkcji o tej samej nazwie w danym fragmencie.
Zainicjuj fragment w aktywności hosta
Po przeniesieniu całej logiki interfejsu do danego fragmentu w działaniu powinna pozostać tylko logika nawigacji.
Kotlin
class ProductListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) } }
Java
public class ProductListActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); } public void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); } }
Ostatnim krokiem jest utworzenie wystąpienia fragmentu w onCreate()
zaraz po ustawieniu widoku treści:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); if (savedInstanceState == null) { ProductListFragment fragment = new ProductListFragment(); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } }
Jak pokazano w tym przykładzie, FragmentManager
automatycznie zapisuje i przywraca fragmenty po zmianach konfiguracji, więc wystarczy dodać fragment tylko wtedy, gdy savedInstanceState
ma wartość null.
Przekaż dodatkowe intencje do fragmentu
Jeśli Twoja aktywność otrzymuje polecenie Extras
w ramach intencji, możesz je przekazać do fragmentu bezpośrednio w postaci argumentów.
W tym przykładzie ProductDetailsFragment
otrzymuje argumenty bezpośrednio od dodatkowych intencji działania:
Kotlin
... if (savedInstanceState == null) { val fragment = ProductDetailsFragment() // Intent extras and Fragment Args are both of type android.os.Bundle. fragment.arguments = intent.extras supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } ...
Java
... if (savedInstanceState == null) { ProductDetailsFragment fragment = new ProductDetailsFragment(); // Intent extras and fragment Args are both of type android.os.Bundle. fragment.setArguments(getIntent().getExtras()); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } ...
Od tego momentu możesz przetestować uruchamianie aplikacji z zastosowaniem fragmentu na pierwszym ekranie. Kontynuuj migrację pozostałych ekranów opartych na aktywności, a po każdej iteracji odczekaj jakiś czas.
Zintegruj komponent Nawigacja
Po korzystaniu z architektury opartej na fragmentach możesz zacząć integrować komponent Nawigacja.
Najpierw dodaj do projektu najnowsze zależności nawigacji, postępując zgodnie z instrukcjami w informacjach o wersji biblioteki nawigacji.
Tworzenie wykresu nawigacyjnego
Komponent Nawigacja przedstawia konfigurację nawigacji w aplikacji w pliku zasobów w postaci wykresu, podobnie jak przedstawiane są w niej widoki aplikacji. Pomaga to utrzymać porządek nawigacji w aplikacji poza bazą kodu i zapewnia wizualne edytowanie nawigacji po aplikacji.
Aby utworzyć wykres nawigacyjny, zacznij od utworzenia nowego folderu zasobów o nazwie navigation
. Aby dodać wykres, kliknij ten katalog prawym przyciskiem myszy i wybierz Nowy element > Plik zasobów nawigacji.
Komponent Nawigacja używa działania jako hosta do nawigacji i zamienia poszczególne fragmenty na tego hosta, gdy użytkownicy poruszają się po aplikacji. Zanim zaczniesz wizualnie układać nawigację po aplikacji, musisz skonfigurować NavHost
w aktywności, która będzie hostować ten wykres. Używamy fragmentów, więc możemy użyć domyślnej implementacji NavHost
komponentu Nawigacja, czyli NavHostFragment
.
Element NavHostFragment
jest konfigurowany przez element FragmentContainerView
umieszczony w aktywności hosta, jak w tym przykładzie:
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Atrybut app:NavGraph
wskazuje wykres nawigacyjny powiązany z tym hostem nawigacji. Ustawienie tej właściwości rozszerza wykres nawigacyjny i ustawia właściwość wykresu na elemencie NavHostFragment
. Atrybut app:defaultNavHost
sprawia, że NavHostFragment
przechwytuje systemowy przycisk Wstecz.
Jeśli korzystasz z nawigacji najwyższego poziomu, np. DrawerLayout
lub BottomNavigationView
, ten element FragmentContainerView
zastępuje główny element widoku treści. Przykłady znajdziesz w artykule Aktualizowanie komponentów interfejsu za pomocą interfejsu NavigationUI.
Aby utworzyć prosty układ, możesz uwzględnić ten element FragmentContainerView
jako element podrzędny elementu podrzędnego ViewGroup
:
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_content"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Jeśli klikniesz kartę Projekt u dołu, zobaczysz wykres podobny do pokazanego poniżej. W lewym górnym rogu wykresu, w sekcji Miejsca docelowe, znajdziesz odniesienie do aktywności NavHost
w formie layout_name (resource_id)
.
Kliknij przycisk plusa u góry ekranu, aby dodać fragmenty do tego wykresu.
Komponent Nawigacja określa poszczególne ekrany jako miejsca docelowe. Miejscami docelowymi mogą być fragmenty, aktywności lub niestandardowe miejsca docelowe. Do wykresu możesz dodać dowolny typ miejsca docelowego, ale pamiętaj, że miejsca docelowe aktywności są uważane za miejsca docelowe w zakończeniu, ponieważ gdy nawigujesz do miejsca docelowego aktywności, działasz na oddzielnym hoście nawigacji i na oddzielnym wykresie.
Komponent Nawigacja odnosi się do działań, czyli sposobu, w jaki użytkownicy docierają z jednego miejsca docelowego do drugiego. Działania mogą też opisywać animacje przejścia i zachowanie popu.
Usuń transakcje fragmentowe
Gdy korzystasz z komponentu Nawigacja, jeśli w ramach tej samej aktywności poruszasz się między ekranami opartymi na fragmentach, możesz usuwać interakcje FragmentManager
.
Jeśli Twoja aplikacja korzysta z wielu fragmentów w ramach tej samej aktywności lub nawigacji najwyższego poziomu, np. za pomocą układu panelu czy nawigacji u dołu, prawdopodobnie używasz interfejsów FragmentManager
i FragmentTransactions
, aby dodawać lub zastępować fragmenty w sekcji głównej treści interfejsu. Można go teraz zastąpić i uprościć w komponencie Nawigacja, udostępniając na wykresie działania łączące miejsca docelowe i poruszając się za pomocą NavController
.
Oto kilka scenariuszy, które mogą wystąpić, wraz z odpowiednimi podejściami do migracji w każdym z tych scenariuszy.
Pojedyncza aktywność zarządzająca wieloma fragmentami
Jeśli masz pojedynczą aktywność, która zarządza wieloma fragmentami, Twój kod aktywności może wyglądać tak:
Kotlin
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Logic to load the starting destination // when the Activity is first created if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit() } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). fun navigateToProductDetail(productId: String) { val fragment = new ProductDetailsFragment() val args = Bundle().apply { putInt(KEY_PRODUCT_ID, productId) } fragment.arguments = args supportFragmentManager.beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit() } }
Java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Logic to load the starting destination when the activity is first created. if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit(); } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). public void navigateToProductDetail(String productId) { Fragment fragment = new ProductDetailsFragment(); Bundle args = new Bundle(); args.putInt(KEY_PRODUCT_ID, productId); fragment.setArguments(args); getSupportFragmentManager().beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit(); } }
W miejscu docelowym źródła możesz wywoływać funkcję nawigacyjną w odpowiedzi na jakieś zdarzenie, jak pokazano poniżej:
Kotlin
class ProductListFragment : Fragment() { ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked // in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the activity to make the transition. private val productClickCallback = ProductClickCallback { product -> (requireActivity() as MainActivity).navigateToProductDetail(product.id) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> ( ((MainActivity) requireActivity()).navigateToProductDetail(product.getId()) ); }
Możesz go zastąpić, aktualizując wykres nawigacyjny tak, aby ustawić miejsce docelowe początkowego i działania, które łączą miejsca docelowe i w razie potrzeby zdefiniuj argumenty:
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_detail" />
</fragment>
<fragment
android:id="@+id/product_detail"
android:name="com.example.android.persistence.ui.ProductDetailFragment"
android:label="Product Detail"
tools:layout="@layout/product_detail">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Potem możesz zaktualizować swoją aktywność:
Kotlin
class MainActivity : AppCompatActivity() { // No need to load the start destination, handled automatically by the Navigation component override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
Java
public class MainActivity extends AppCompatActivity { // No need to load the start destination, handled automatically by the Navigation component @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
Aktywność nie wymaga już metody navigateToProductDetail()
. W następnej sekcji zaktualizujemy ProductListFragment
, aby umożliwić przejście do następnego ekranu ze szczegółami produktu za pomocą NavController
.
Bezpieczne przekazywanie argumentów
Komponent Nawigacja ma wtyczkę do Gradle o nazwie Safe Args, która generuje proste klasy obiektów i konstruktora, aby zapewnić bezpieczny dostęp do argumentów określonych dla miejsc docelowych i działań.
Po zastosowaniu wtyczki dowolne argumenty zdefiniowane w miejscu docelowym na wykresie nawigacyjnym powodują, że platforma komponentów nawigacji generuje klasę Arguments
, która dostarcza bezpieczne argumenty typu bezpieczne argumenty do miejsca docelowego.
Po zdefiniowaniu działania wtyczka generuje klasę konfiguracji Directions
, która może wskazywać usłudze NavController
, jak ma skierować użytkownika do miejsca docelowego. Gdy działanie wskazuje miejsce docelowe wymagające argumentów, wygenerowana klasa Directions
zawiera metody konstruktora, które wymagają tych parametrów.
Wewnątrz tego fragmentu użyj funkcji NavController
i wygenerowanej klasy Directions
, aby podać do miejsca docelowego argumenty bezpieczne dla typu, jak w tym przykładzie:
Kotlin
class ProductListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the NavController to make the transition. private val productClickCallback = ProductClickCallback { product -> val directions = ProductListDirections.navigateToProductDetail(product.id) findNavController().navigate(directions) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> { ProductListDirections.ViewProductDetails directions = ProductListDirections.navigateToProductDetail(product.getId()); NavHostFragment.findNavController(this).navigate(directions); }; }
Nawigacja najwyższego poziomu
Jeśli Twoja aplikacja korzysta z interfejsu DrawerLayout
, być może masz w swojej aktywności wiele funkcji konfiguracyjnych, które umożliwiają otwieranie i zamykanie szuflady oraz przechodzenie do innych miejsc docelowych.
Aktywność może wyglądać np. tak:
Kotlin
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar: Toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) val navView: NavigationView = findViewById(R.id.nav_view) val toggle = ActionBarDrawerToggle( this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close ) drawerLayout.addDrawerListener(toggle) toggle.syncState() navView.setNavigationItemSelectedListener(this) } override fun onBackPressed() { val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) if (drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.closeDrawer(GravityCompat.START) } else { super.onBackPressed() } } override fun onNavigationItemSelected(item: MenuItem): Boolean { // Handle navigation view item clicks here. when (item.itemId) { R.id.home -> { val homeFragment = HomeFragment() show(homeFragment) } R.id.gallery -> { val galleryFragment = GalleryFragment() show(galleryFragment) } R.id.slide_show -> { val slideShowFragment = SlideShowFragment() show(slideShowFragment) } R.id.tools -> { val toolsFragment = ToolsFragment() show(toolsFragment) } } val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) drawerLayout.closeDrawer(GravityCompat.START) return true } } private fun show(fragment: Fragment) { val drawerLayout = drawer_layout as DrawerLayout val fragmentManager = supportFragmentManager fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit() drawerLayout.closeDrawer(GravityCompat.START) }
Java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); NavigationView navigationView = findViewById(R.id.nav_view); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); navigationView.setNavigationItemSelectedListener(this); } @Override public void onBackPressed() { DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } @Override public boolean onNavigationItemSelected(MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.home) { Fragment homeFragment = new HomeFragment(); show(homeFragment); } else if (id == R.id.gallery) { Fragment galleryFragment = new GalleryFragment(); show(galleryFragment); } else if (id == R.id.slide_show) { Fragment slideShowFragment = new SlideShowFragment(); show(slideShowFragment); } else if (id == R.id.tools) { Fragment toolsFragment = new ToolsFragment(); show(toolsFragment); } DrawerLayout drawer = findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } private void show(Fragment fragment) { DrawerLayout drawerLayout = findViewById(R.id.drawer_layout); FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit(); drawerLayout.closeDrawer(GravityCompat.START); } }
Gdy dodasz do projektu komponent Nawigacja i utworzysz wykres nawigacyjny, dodaj wszystkie miejsca docelowe treści z wykresu (np. Strona główna, Galeria, Pokaz slajdów i Narzędzia z powyższego przykładu). Sprawdź, czy wartości id
pozycji menu są zgodne z powiązanymi wartościami id
miejsca docelowego, jak pokazano poniżej:
<!-- activity_main_drawer.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/home"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_home" />
<item
android:id="@+id/gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery" />
<item
android:id="@+id/slide_show"
android:icon="@drawable/ic_menu_slideshow"
android:title="@string/menu_slideshow" />
<item
android:id="@+id/tools"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools" />
</group>
</menu>
<!-- activity_main_graph.xml -->
<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/main_graph"
app:startDestination="@id/home">
<fragment
android:id="@+id/home"
android:name="com.example.HomeFragment"
android:label="Home"
tools:layout="@layout/home" />
<fragment
android:id="@+id/gallery"
android:name="com.example.GalleryFragment"
android:label="Gallery"
tools:layout="@layout/gallery" />
<fragment
android:id="@+id/slide_show"
android:name="com.example.SlideShowFragment"
android:label="Slide Show"
tools:layout="@layout/slide_show" />
<fragment
android:id="@+id/tools"
android:name="com.example.ToolsFragment"
android:label="Tools"
tools:layout="@layout/tools" />
</navigation>
Jeśli dopasowujesz wartości id
w menu i na wykresie, możesz połączyć NavController
dla tej aktywności, aby automatycznie obsługiwała nawigację na podstawie pozycji menu. NavController
obsługuje też otwieranie i zamykanie elementu DrawerLayout
oraz prawidłową obsługę przycisków strzałek w górę i w górę.
Następnie możesz zaktualizować urządzenie MainActivity
, aby połączyć je z NavController
do Toolbar
i NavigationView
.
Oto przykład tego fragmentu:
Kotlin
class MainActivity : AppCompatActivity() { val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) } val navController by lazy { (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController } val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById<Toolbar>(R.id.toolbar) setSupportActionBar(toolbar) // Show and Manage the Drawer and Back Icon setupActionBarWithNavController(navController, drawerLayout) // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. navigationView.setupWithNavController(navController) } override fun onSupportNavigateUp(): Boolean { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation return navController.navigateUp(drawerLayout) } }
Java
public class MainActivity extends AppCompatActivity { private DrawerLayout drawerLayout; private NavController navController; private NavigationView navigationView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = findViewById(R.id.drawer_layout); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); navController = navHostFragment.getNavController(); navigationView = findViewById(R.id.nav_view); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); // Show and Manage the Drawer and Back Icon NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout); // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. NavigationUI.setupWithNavController(navigationView, navController); } @Override public boolean onSupportNavigateUp() { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation. return NavigationUI.navigateUp(navController, drawerLayout); } }
Tej samej metody możesz używać zarówno w przypadku nawigacji w oparciu o dolną część nawigacji, jak i z nawigacją przy użyciu menu. Więcej przykładów znajdziesz w artykule o aktualizowaniu komponentów interfejsu za pomocą NavigationUI.
Dodawanie miejsc docelowych aktywności
Gdy każdy ekran w aplikacji jest podłączony do komponentu Nawigacja i nie używasz już FragmentTransactions
do przechodzenia między miejscami docelowymi opartymi na fragmentach, kolejnym krokiem jest wyeliminowanie wywołań startActivity
.
Najpierw określ miejsca w aplikacji, w których masz 2 osobne wykresy nawigacji i których używasz startActivity
do przechodzenia między nimi.
Ten przykład zawiera 2 wykresy (A i B) oraz wywołanie startActivity()
wskazujące przejście z punktu A do B.
Kotlin
fun navigateToProductDetails(productId: String) { val intent = Intent(this, ProductDetailsActivity::class.java) intent.putExtra(KEY_PRODUCT_ID, productId) startActivity(intent) }
Java
private void navigateToProductDetails(String productId) { Intent intent = new Intent(this, ProductDetailsActivity.class); intent.putExtra(KEY_PRODUCT_ID, productId); startActivity(intent);
Następnie zastąp je miejscem docelowym aktywności na wykresie A, które reprezentuje przejście do działania hosta na wykresie B. Jeśli masz argumenty, które chcesz przekazać do początkowego punktu docelowego grafu B, możesz je wskazać w definicji miejsca docelowego aktywności.
W poniższym przykładzie wykres A definiuje miejsce docelowe działania, które przyjmuje argument product_id
wraz z działaniem. Wykres B nie zawiera żadnych zmian.
Reprezentacja XML wykresów A i B może wyglądać tak:
<!-- Graph A -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list_fragment">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details"
tools:layout="@layout/product_details_host">
<argument
android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<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"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details_fragment">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Do działania hosta na wykresie B możesz przejść, korzystając z tych samych mechanizmów, co podczas przechodzenia do miejsc docelowych fragmentów:
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
Przekazywanie argumentów miejsca docelowego aktywności do fragmentu miejsca docelowego początkowego
Jeśli aktywność w miejscu docelowym otrzymuje dodatki, tak jak w poprzednim przykładzie, możesz przekazać je bezpośrednio do miejsca docelowego jako argumenty, ale musisz ręcznie ustawić wykres nawigacyjny hosta w metodzie onCreate()
działania hosta, aby przekazać dodatkowe intencje jako argumenty do fragmentu, jak widać poniżej:
Kotlin
class ProductDetailsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_details_host) val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment val navController = navHostFramgent.navController navController .setGraph(R.navigation.product_detail_graph, intent.extras) } }
Java
public class ProductDetailsActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_details_host); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); NavController navController = navHostFragment.getNavController(); navController .setGraph(R.navigation.product_detail_graph, getIntent().getExtras()); } }
Dane można pobierać z argumentów fragmentu Bundle
za pomocą wygenerowanej klasy argumentów, jak w tym przykładzie:
Kotlin
class ProductDetailsFragment : Fragment() { val args by navArgs<ProductDetailsArgs>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val productId = args.productId ... } ...
Java
public class ProductDetailsFragment extends Fragment { ProductDetailsArgs args; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); args = ProductDetailsArgs.fromBundle(requireArguments()); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { int productId = args.getProductId(); ... } ...
Łączenie aktywności
Wykresy nawigacyjne możesz łączyć w przypadkach, gdy wiele działań ma ten sam układ, np. prosty element FrameLayout
zawierający 1 fragment. W większości takich przypadków wystarczy połączyć wszystkie elementy z każdego wykresu nawigacyjnego i zaktualizować dowolne elementy miejsc docelowych aktywności w miejsca docelowe fragmentów.
Ten przykład stanowi połączenie wykresów A i B z poprzedniej sekcji:
Przed połączeniem:
<!-- Graph A -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details Host"
tools:layout="@layout/product_details_host">
<argument android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<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/product_detail_graph"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Po połączeniu:
<!-- Combined Graph A and B -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details" />
</fragment>
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
Pozostawienie takich samych nazw działań podczas scalania może sprawić, że będzie to bezproblemowy proces, który nie będzie wymagał żadnych zmian w bieżącej bazie kodu. Na przykład wartość navigateToProductDetail
pozostaje bez zmian. Jedyna różnica polega na tym, że to działanie reprezentuje teraz nawigację do miejsca docelowego fragmentu w tej samej domenie NavHost
, a nie do miejsca docelowego aktywności:
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
Dodatkowe materiały
Więcej informacji związanych z nawigacją znajdziesz w tych tematach:
- Aktualizowanie komponentów interfejsu za pomocą nawigacji – dowiedz się, jak zarządzać nawigacją za pomocą górnego paska aplikacji, panelu nawigacji i dołu nawigacji.
- Nawigacja testowa – dowiedz się, jak przetestować przepływy pracy nawigacji w aplikacji.