Migracja do komponentu Nawigacja

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:

  1. 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.

  2. 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.

  3. Dodawanie miejsc docelowych aktywności – zastępuj połączenia w startActivity() działaniami, które korzystają z miejsc docelowych aktywności.

  4. Łą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: