Migracja do komponentu Nawigacja

Komponent Nawigacja to biblioteka, która może zarządzać złożoną nawigacją, animacją przejścia, precyzyjnymi linkami, oraz argumentu sprawdzany podczas kompilowania przekazywany między ekranami aplikacji.

Ten dokument służy jako ogólny przewodnik po migracji istniejącej aplikacji do przy użyciu komponentu Nawigacja.

Ogólnie migracja obejmuje te czynności:

  1. Przenoszenie logiki UI z ekranu poza działania – przenieś interfejs aplikacji logują się w działaniach, tak by każda z nich posiadała wyłącznie logikę komponenty interfejsu globalnego nawigacji, takie jak Toolbar, podczas przekazywania implementacji poszczególnych ekranów we fragmencie lub niestandardowym miejscu docelowym.

  2. Zintegruj komponent nawigacji – w przypadku każdego działania utwórz plik graf nawigacji zawierający co najmniej jeden fragment zarządzany przez ten działania. Zastąp transakcje związane z fragmentami operacjami komponentu Nawigacja.

  3. Dodaj miejsca docelowe aktywności – zamień połączenia (startActivity()) na za pomocą miejsc docelowych aktywności.

  4. Połącz aktywności – łącz wykresy nawigacyjne w przypadkach, gdy: różne działania mają wspólny układ.

.

Wymagania wstępne

W tym przewodniku przyjęto założenie, że aplikacja została już przeniesiona, aby mogła korzystać AndroidX. W przeciwnym razie migrację swojego projektu, by można było zacząć korzystać z AndroidaX i kontynuuję.

Logi UI specyficzne dla ekranu zostaje usunięta z działań

Aktywności to komponenty na poziomie systemu, które ułatwiają interakcję graficzną między aplikacją a Androidem. Aktywności są zarejestrowane w pliku manifestu aplikacji. aby Android wie, jakie działania są dostępne do uruchomienia. Aktywność pozwala aplikacji na reagowanie również na zmiany w Androidzie, np. gdy interfejs aplikacji wkracza na pierwszy plan lub go opuszcza, obraca się itd. aktywność może też służyć jako miejsce, stan udostępniania między ekranami.

Aktywności w kontekście aplikacji powinny służyć jako host do nawigacji i powinni dysponować wiedzą i logiką przechodzenia między ekranami, i tak dalej. Lepiej jednak zarządzać szczegółami interfejsu do mniejszej części interfejsu, którą można wykorzystać wielokrotnie. Zalecana implementacja wzór to fragmenty. Zobacz Pojedyncza aktywność: dlaczego, kiedy i jak , aby dowiedzieć się więcej o zaletach używania fragmentów. Nawigacja obsługuje fragmenty za pomocą zależności fragmentu nawigacji. Nawigacja obsługuje też niestandardowych typów miejsc docelowych.

Jeśli aplikacja nie korzysta z fragmentów, najpierw musisz przeprowadzić migrację. na każdym ekranie aplikacji, aby użyć fragmentu. Nie usuwasz aktywności o w tym punkcie. Zamiast tego tworzysz fragment reprezentujący ekran i przerwy logikę interfejsu użytkownika przez odpowiedzialność.

Przedstawiamy fragmenty

Aby zilustrować proces wprowadzania fragmentów, zacznijmy od przykładu aplikacji składającej się z 2 ekranów: listy produktów i Szczegóły produktu. Po kliknięciu usługi na ekranie listy na ekran ze szczegółowymi informacjami.

W tym przykładzie ekrany z listą i szczegółami są obecnie oddzielnymi działaniami.

Utwórz nowy układ do hostowania interfejsu użytkownika

Aby przedstawić fragment, utwórz nowy plik układu dla aktywności go hostować. Spowoduje to zastąpienie obecnego układu widoku treści aktywności.

Aby uzyskać prosty widok, możesz użyć elementu FrameLayout, jak pokazano poniżej przykładowy 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 dodajemy atrybuty fragment.

Następnie w funkcji onCreate() aktywności zmodyfikuj odwołanie do pliku układu w funkcji onCreate aktywności, aby wskazać 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);
        ...
    }
}

Istniejący układ (w tym przykładzie product_list) jest używany jako widok główny dla fragmentu, który chcesz utworzyć.

Tworzenie fragmentu

Utwórz nowy fragment, aby zarządzać interfejsem Twojego ekranu. Dobrą praktyką jest muszą być spójne z nazwą hosta aktywności. W poniższym fragmencie kodu użyto 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

Gdy masz już ustaloną definicję fragmentu, następnym krokiem jest przeniesienie logiki UI z tego ekranu do nowego fragmentu. Jeśli przechodzisz z architektura oparta na aktywności, masz sporą logikę tworzenia widoków w ramach funkcji onCreate() dotyczącej Twojej aktywności.

Oto przykładowy ekran aktywności z elementami interfejsu, które musimy 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
   }

Aktywność może również wpływać na to, kiedy i w jaki sposób użytkownik przechodzi do strony na następnym ekranie, tak jak w przykładzie poniżej:

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

We fragmencie należy podzielić tę pracę między onCreateView() oraz onViewCreated(), a w aktywności pozostaje tylko nawigacja:

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 aplikacji ProductListFragment nie ma wywołania setContentView(). aby rozszerzyć i połączyć układ. We fragmencie onCreateView() inicjuje metodę widoku głównym. onCreateView() pobiera instancję LayoutInflater, która może być używana do: zwiększyć widok główny na podstawie pliku zasobów układu. W tym przykładzie użyto ponownie istniejący układ product_list, który był używany przez aktywność, ponieważ nic musi zmienić się na sam układ.

Jeśli masz jakąś logikę interfejsu w funkcjach onStart(), onResume() dotyczących Twojej aktywności, funkcji onPause() lub onStop(), które nie są związane z nawigacją, możesz przenieść je do odpowiednich funkcji o tej samej nazwie we fragmencie.

Zainicjuj fragment w działaniu hosta

Po przeniesieniu całej logiki interfejsu do tego fragmentu można przechodzić tylko nie powinny mieć zastosowania żadne wartości logiczne.

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 elemencie onCreate(), 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 widać w tym przykładzie, FragmentManager automatycznie zapisuje i przywraca fragmentami w czasie zmian konfiguracji, więc wystarczy dodać fragment, jeśli savedInstanceState ma wartość null.

Przekazywanie dodatkowych intencji do fragmentu

Jeśli Twoja aktywność otrzymuje Extras poprzez intencję, możesz przekazać je usłudze fragment bezpośrednio jako argumenty.

W tym przykładzie ProductDetailsFragment otrzymuje swoje argumenty bezpośrednio z dodatkowych informacji o zamiarze 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();
}

...

Teraz możesz już przetestować działanie aplikacji na pierwszym ekranie. zaktualizowany o fragment z fragmentem. Kontynuuj przenoszenie pozostałej części konta na podstawie aktywności. ekranów, a po każdej iteracji czas na testowanie.

Zintegruj komponent nawigacji

Po zastosowaniu architektury opartej na fragmentach możesz przystąpić do integracji komponentu Nawigacja.

Najpierw dodaj do projektu najnowsze zależności nawigacji po instrukcji w Informacje o wersji biblioteki nawigacji

Tworzenie wykresu nawigacyjnego

Komponent Nawigacja reprezentuje konfigurację nawigacji w aplikacji w w postaci wykresu, podobnie jak wyświetlenia aplikacji. To pomaga uporządkowanie nawigacji w aplikacji poza bazą kodu za pomocą której możesz wizualnie edytować nawigację po aplikacji.

Aby utworzyć wykres nawigacyjny, zacznij od utworzenia nowego folderu zasobów o nazwie navigation Aby dodać wykres, kliknij katalog prawym przyciskiem myszy i wybierz Nowe > Plik zasobów nawigacyjnych.

Komponent Nawigacja używa aktywności jako host do nawigacji i zamienia poszczególne fragmenty na tego hosta, gdy użytkownicy będą przeglądać witryny. do aplikacji. Zanim zaczniesz tworzyć wizualne układy nawigacji w aplikacji, Chcę skonfigurować element NavHost w aktywności, która będzie go hostować wykres. Ponieważ korzystamy z fragmentów, możemy użyć komponentu Nawigacja domyślna implementacja NavHost, NavHostFragment

Interfejs NavHostFragment jest konfigurowany za pomocą interfejsu FragmentContainerView umieszczone 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 elementem hosta nawigacji. Ustawienie tej właściwości powoduje rozwinięcie wykresu nawigacyjnego i ustawienie wykresu. usłudze w domenie NavHostFragment. Atrybut app:defaultNavHost zapewnia że NavHostFragment przechwytuje systemowy przycisk Wstecz.

Jeśli korzystasz z nawigacji najwyższego poziomu, np. DrawerLayout lub BottomNavigationView, to FragmentContainerView zastępuje główny element widoku treści. Zobacz Aktualizowanie komponentów interfejsu za pomocą NavigationUI .

W przypadku prostego układu możesz użyć tego elementu: FragmentContainerView 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 na dole, zobaczysz wykres podobny do tego. do pokazanego poniżej. W lewym górnym rogu wykresu, w kolumnie Miejsca docelowe, możesz zobaczyć odwołanie do aktywności NavHost w formularzu z layout_name (resource_id).

Kliknij przycisk plusa w górnej części ekranu, aby dodać fragmenty do tego wykresu.

Komponent Nawigacja określa poszczególne ekrany jako miejsca docelowe. Miejscami docelowymi mogą być fragmenty, działania lub niestandardowe miejsca docelowe. Możesz dodać dowolny typ miejsca docelowego na wykresie. Pamiętaj jednak, że miejsca docelowe aktywności to są uznawane za miejsca docelowe terminala, ponieważ po przejściu do określonej aktywności miejsce docelowe, działasz na osobnym hoście nawigacji i wykresie.

Komponent Nawigacja odnosi się do sposobu, w jaki użytkownicy jako działania. Działanie może też opisywać przejście animacji i działań elementów wyskakujących.

Usuń transakcje związane z fragmentami

Używasz teraz komponentu Nawigacja i jeśli nawigujesz pomiędzy ekranami opartymi na fragmentach w ramach tej samej aktywności, możesz usunąć FragmentManager interakcje.

Jeśli aplikacja używa wielu fragmentów w ramach tej samej aktywności lub na najwyższym poziomie takich jak układ szuflady czy dolna nawigacja, za pomocą funkcji FragmentManager i FragmentTransactions. , aby dodać lub zastąpić fragmenty w sekcji z zawartością główną interfejsu użytkownika. Może teraz można zastąpić i uprościć za pomocą komponentu Nawigacja aby połączyć miejsca docelowe na wykresie, a następnie użyj NavController

Oto kilka scenariuszy, jakie możesz napotkać, wraz z możliwymi podejściami dla każdego scenariusza.

Pojedyncza aktywność zarządzająca wieloma fragmentami

Jeśli masz jedną aktywność, która zarządza wieloma fragmentami, aktywność kod 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 źródłowym miejscu docelowym możesz wywoływać funkcję nawigacyjną odpowiedź na wybrane zdarzenie, na przykład:

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żna to zastąpić, aktualizując wykres nawigacji tak, aby zawierał początkowe miejsce docelowe i działania, które łączą miejsca docelowe i określają, – gdy są wymagane – 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>

Następnie 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 ciągu następnych aktualizujemy ProductListFragment tak, by używał NavController do nawigacji przejdź do kolejnego ekranu ze szczegółami produktu.

Bezpiecznie przekazuj argumenty

Komponent Nawigacja ma wtyczkę do Gradle o nazwie Bezpieczne argumenty który generuje proste klasy obiektów i konstruktora zapewniające dostęp argumentów miejsc docelowych i czynności.

Po zastosowaniu wtyczki wszystkie argumenty zdefiniowane w miejscu docelowym w powoduje, że platforma komponentu Nawigacja generuje Klasa Arguments, która udostępnia bezpieczne argumenty typu w miejscu docelowym. Zdefiniowanie działania powoduje wygenerowanie przez wtyczkę konfiguracji Directions klasa, która może pomóc w poinformowaniu NavController o tym, jak dotrzeć do użytkownika miejsce docelowe. Gdy działanie wskazuje miejsce docelowe, które wymaga argumentów, generowana klasa Directions zawiera metody konstruktora, które tych parametrów.

We fragmencie użyj kodu NavController i wygenerowanej klasy Directions, aby należy podać argumenty bezpieczne do typu w miejscu docelowym, jak w poniższym przykładzie przykład:

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 używa interfejsu DrawerLayout, konfiguracja może być obszerna które polega na otwarciu i zamknięciu panelu oraz przejściu do innych miejscach docelowych.

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

Po dodaniu komponentu Nawigacja do projektu i utworzeniu dodaj do niego wszystkie miejsca docelowe treści (np. Strona główna, Galeria, Pokaz slajdów i Narzędzia z powyższego przykładu). Upewnij się, aby wartości id pozycji w menu były zgodne z powiązanymi wartościami parametru id miejsca docelowego, jak 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 pasują do wartości id w menu i na wykresie, możesz połączyć NavController dla tej aktywności, aby automatycznie obsługiwać nawigację na podstawie tę pozycję w menu. NavController obsługuje również otwieranie i zamykanie DrawerLayout oraz prawidłowe działanie przycisków Wstecz i Wstecz.

Możesz zaktualizować urządzenie MainActivity, by podłączyć NavController do Toolbar i NavigationView.

Oto przykład:

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 techniki możesz używać zarówno w przypadku nawigacji opartej na bottomNavigationView, i nawigację w menu. Zobacz Aktualizowanie komponentów interfejsu za pomocą NavigationUI .

Dodaj miejsca docelowe aktywności

po podłączeniu każdego ekranu aplikacji do komponentu Nawigacja. nie używasz już aplikacji FragmentTransactions do przełączania się między miejsca docelowe oparte na fragmentach, następnym krokiem jest wyeliminowanie startActivity połączeń.

Najpierw znajdź miejsca w aplikacji, w których masz 2 osobne wykresy nawigacyjne i używają usługi startActivity do przechodzenia między nimi.

Ten przykład zawiera dwa wykresy (A i B) oraz wywołanie startActivity() funkcji 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 na wykresie A miejscem docelowym aktywności, które odpowiada przejście do aktywności hosta na wykresie B. Jeśli masz argumenty przekazywane do funkcji początkowym punktem wykresu B, możesz wyznaczyć je w miejscu docelowym aktywności definicji.

W poniższym przykładzie graf A definiuje miejsce docelowe aktywności, które pobiera product_id argument wraz z działaniem. Wykres B nie zawiera żadnych zmian.

Reprezentacja grafów A i B w formacie XML 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>

Możesz przejść do aktywności hosta z grafu B za pomocą tych samych mechanizmów służy do 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);

Przekaż argumenty miejsca docelowego aktywności do początkowego fragmentu docelowego

Jeśli działanie w miejscu docelowym otrzymuje dodatki, tak jak w poprzednim przykładzie, możesz przekazać je do miejsca docelowego bezpośrednio jako argumenty, ale trzeba ręcznie ustawić wykres nawigacji gospodarza w sekcji onCreate(), dzięki czemu możesz przekazywać dodatkowe intencji jako argumenty do funkcji 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 wyodrębnić z argumentów fragmentu Bundle za pomocą generowana klasa 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żna łączyć w przypadku, gdy wiele aktywności ma taki sam ten sam układ, na przykład prosty element FrameLayout zawierający pojedynczy fragment. W W większości przypadków wystarczy połączyć wszystkie elementy wykresu nawigacji i aktualizowanie elementów docelowych aktywności, aby dzielić je na fragmenty miejsca docelowe.

Ten przykład łączy wykresy 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>

Użycie takich samych nazw działań podczas scalania może sprawić, że proces ten będzie przebiegał płynnie nie wymagając przy tym żadnych zmian w Twojej bazie kodu. Przykład: navigateToProductDetail pozostaje tutaj bez zmian. Jedyna różnica jest taka, to działanie reprezentuje teraz nawigację do miejsca docelowego fragmentu w obrębie tego samego NavHost zamiast 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: