Zur Navigationskomponente migrieren

Die Navigationskomponente ist eine Bibliothek, mit der komplexe Navigationen, Übergangsanimationen, Deeplinks und Argumente nach der Kompilierungszeit zwischen den Bildschirmen in Ihrer App übergeben werden können.

Dieses Dokument dient als allgemeine Anleitung für die Migration einer vorhandenen Anwendung zur Verwendung der Navigationskomponente.

Grundsätzlich umfasst die Migration folgende Schritte:

  1. Bildschirmspezifische UI-Logik aus Aktivitäten entfernen – Verschieben Sie die UI-Logik Ihrer App aus den Aktivitäten und stellen Sie sicher, dass jede Aktivität nur die Logik von globalen Navigations-UI-Komponenten wie Toolbar besitzt, während die Implementierung jedes Bildschirms an ein Fragment oder ein benutzerdefiniertes Ziel delegiert wird.

  2. Navigationskomponente integrieren: Erstellen Sie für jede Aktivität ein Navigationsdiagramm, das mindestens ein Fragment enthält, das von dieser Aktivität verwaltet wird. Ersetzen Sie Fragmenttransaktionen durch Vorgänge der Navigationskomponente.

  3. Aktivitätsziele hinzufügen: Ersetzen Sie startActivity()-Aufrufe durch Aktionen, die Aktivitätsziele verwenden.

  4. Aktivitäten kombinieren: Sie können Navigationsdiagramme kombinieren, wenn mehrere Aktivitäten ein gemeinsames Layout haben.

Voraussetzungen

In diesem Leitfaden wird davon ausgegangen, dass Sie Ihre App bereits zur Verwendung von AndroidX-Bibliotheken migriert haben. Falls noch nicht geschehen, migrieren Sie Ihr Projekt, um Android X zu verwenden, bevor Sie fortfahren.

Bildschirmspezifische UI-Logik aus Aktivitäten verschieben

Aktivitäten sind Komponenten auf Systemebene, die eine grafische Interaktion zwischen Ihrer App und Android ermöglichen. Aktivitäten werden im Manifest deiner App registriert, damit Android weiß, welche Aktivitäten für den Start verfügbar sind. Mit der Aktivitätsklasse kann Ihre App auch auf Android-Änderungen reagieren, z. B. wenn die UI der App in den Vordergrund eintritt oder sie verlässt, rotiert usw. Die Aktivität kann auch dazu dienen, den Status zwischen Bildschirmen zu teilen.

Im Kontext Ihrer Anwendung sollten Aktivitäten als Host für die Navigation dienen und die Logik und das Wissen für den Wechsel zwischen Bildschirmen, die Übergabe von Daten usw. enthalten. Die Verwaltung der Details Ihrer UI ist jedoch besser, wenn Sie einem kleineren, wiederverwendbaren Teil Ihrer UI überlassen. Die empfohlene Implementierung für dieses Muster ist Fragmente. Weitere Informationen zu den Vorteilen der Verwendung von Fragmenten finden Sie unter Einzelaktivität: Warum, Wann und Wie. Die Navigation unterstützt Fragmente über die Abhängigkeit navigation-fragment. Die Navigation unterstützt auch benutzerdefinierte Zieltypen.

Wenn Ihre Anwendung keine Fragmente verwendet, müssen Sie zuerst jeden Bildschirm in der Anwendung migrieren, um ein Fragment zu verwenden. Sie entfernen die Aktivität zu diesem Zeitpunkt nicht. Stattdessen erstellen Sie ein Fragment, das den Bildschirm darstellt und dabei die UI-Logik nach Verantwortung aufteilt.

Jetzt neu: Fragmente

Das Einführen von Fragmenten wird anhand eines Beispiels für eine Anwendung veranschaulicht, die aus zwei Bildschirmen besteht: einem Bildschirm Produktliste und einem Bildschirm Produktdetails. Durch Klicken auf ein Produkt in der Liste gelangen die Nutzenden zu einem Detailbildschirm mit weiteren Informationen.

In diesem Beispiel sind die Bildschirme „Liste“ und „Details“ derzeit separate Aktivitäten.

Neues Layout für das Hosten der Benutzeroberfläche erstellen

Wenn Sie ein Fragment einführen möchten, erstellen Sie zuerst eine neue Layoutdatei für die Aktivität, auf der das Fragment gehostet wird. Dies ersetzt das aktuelle Layout der Inhaltsansicht der Aktivität.

Für eine einfache Ansicht können Sie einen FrameLayout verwenden, wie im folgenden Beispiel-product_list_host gezeigt:

<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" />

Das Attribut id bezieht sich auf den Inhaltsabschnitt, in dem wir später das Fragment hinzufügen.

Ändern Sie als Nächstes in der Funktion onCreate() Ihrer Aktivität die Layoutdateireferenz in der Funktion onCreate Ihrer Aktivität so, dass sie auf diese neue Layoutdatei verweist:

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

Das vorhandene Layout (in diesem Beispiel product_list) wird als Stammansicht für das Fragment verwendet, das Sie erstellen möchten.

Fragment erstellen

Erstelle ein neues Fragment, um die Benutzeroberfläche für deinen Bildschirm zu verwalten. Es empfiehlt sich, den Namen Ihres Aktivitätshosts einheitlich zu verwenden. Im folgenden Snippet wird ProductListFragment verwendet. Beispiel:

Kotlin

class ProductListFragment : Fragment() {
    // Leave empty for now.
}

Java

public class ProductListFragment extends Fragment {
    // Leave empty for now.
}

Aktivitätslogik in ein Fragment verschieben

Nachdem die Fragmentdefinition festgelegt ist, muss im nächsten Schritt die UI-Logik für diesen Bildschirm aus der Aktivität in dieses neue Fragment verschoben werden. Wenn Sie von einer aktivitätsbasierten Architektur kommen, gibt es wahrscheinlich eine Menge Logik zum Erstellen von Ansichten in der onCreate()-Funktion Ihrer Aktivität.

Hier ist ein Beispiel für einen aktivitätsbasierten Bildschirm mit UI-Logik, den wir verschieben müssen:

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
   }

Ihre Aktivität kann auch steuern, wann und wie der Nutzer zum nächsten Bildschirm navigiert, wie im folgenden Beispiel gezeigt:

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

Innerhalb des Fragments verteilen Sie diese Arbeit auf onCreateView() und onViewCreated(), wobei nur die Navigationslogik in der Aktivität verbleibt:

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

In ProductListFragment wird setContentView() nicht aufgerufen, um das Layout zu erhöhen und zu verbinden. In einem Fragment initialisiert onCreateView() die Stammansicht. onCreateView() verwendet eine Instanz eines LayoutInflater, mit dem die Stammansicht basierend auf einer Layout-Ressourcendatei erhöht werden kann. In diesem Beispiel wird das bestehende product_list-Layout wiederverwendet, das von der Aktivität verwendet wurde, da das Layout selbst nicht geändert werden muss.

Wenn sich in den Funktionen onStart(), onResume(), onPause() oder onStop() Ihrer Aktivität UI-Logik befindet, die nichts mit der Navigation zu tun hat, können Sie diese zu entsprechenden Funktionen mit demselben Namen im Fragment verschieben.

Fragment in der Hostaktivität initialisieren

Sobald Sie die gesamte UI-Logik nach unten zum Fragment verschoben haben, sollte nur noch die Navigationslogik in der Aktivität verbleiben.

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

Nachdem Sie die Inhaltsansicht festgelegt haben, erstellen Sie im letzten Schritt in onCreate() eine Instanz des Fragments:

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

Wie in diesem Beispiel gezeigt, speichert und stellt FragmentManager Fragmente bei Konfigurationsänderungen automatisch wieder her. Sie müssen das Fragment also nur hinzufügen, wenn savedInstanceState null ist.

Intent-Extras an das Fragment übergeben

Wenn Ihre Aktivität über einen Intent Extras erhält, können Sie diese direkt als Argumente an das Fragment übergeben.

In diesem Beispiel erhält der ProductDetailsFragment seine Argumente direkt von den Intent-Extras der Aktivität:

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

...

Jetzt sollten Sie die Ausführung Ihrer Anwendung testen können, wenn der erste Bildschirm für die Verwendung eines Fragments aktualisiert wurde. Fahren Sie mit der Migration der übrigen aktivitätsbasierten Bildschirme fort und nehmen Sie sich nach jeder Iteration Zeit für Tests.

Komponente „Navigation“ integrieren

Sobald Sie eine fragmentbasierte Architektur verwenden, können Sie mit der Integration der Navigationskomponente beginnen.

Fügen Sie Ihrem Projekt zuerst die neuesten Navigationsabhängigkeiten hinzu. Folgen Sie dazu der Anleitung in den Versionshinweisen zur Navigationsbibliothek.

Navigationsdiagramm erstellen

Die Navigationskomponente stellt die Navigationskonfiguration Ihrer App in einer Ressourcendatei als Diagramm dar, ähnlich wie die Ansichten Ihrer App. So bleibt die Navigation Ihrer App außerhalb der Codebasis organisiert und Sie können die App-Navigation visuell bearbeiten.

Zum Erstellen eines Navigationsdiagramms erstellen Sie zuerst einen neuen Ressourcenordner mit dem Namen navigation. Klicken Sie zum Hinzufügen der Grafik mit der rechten Maustaste auf dieses Verzeichnis und wählen Sie Neu > Navigationsressourcendatei aus.

Die Navigationskomponente verwendet eine Aktivität als Host für die Navigation und tauscht einzelne Fragmente in diesen Host aus, wenn Nutzer durch Ihre App navigieren. Bevor Sie damit beginnen können, das Layout der App-Navigation visuell darzustellen, müssen Sie innerhalb der Aktivität, die dieses Diagramm hosten soll, eine NavHost konfigurieren. Da wir Fragmente verwenden, können wir die NavHost-Standardimplementierung der Navigationskomponente, NavHostFragment, verwenden.

Ein NavHostFragment wird über ein FragmentContainerView konfiguriert, das sich in einer Hostaktivität befindet, wie im folgenden Beispiel gezeigt:

<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" />

Das Attribut app:NavGraph verweist auf die Navigationsgrafik, die diesem Navigationshost zugeordnet ist. Wenn Sie diese Eigenschaft festlegen, wird das Navigationsmenü aufgebläht und das Diagrammattribut wird für NavHostFragment festgelegt. Das Attribut app:defaultNavHost sorgt dafür, dass die NavHostFragment die Systemschaltfläche „Zurück“ abfängt.

Wenn Sie Navigationselemente auf oberster Ebene wie DrawerLayout oder BottomNavigationView verwenden, ersetzt diese FragmentContainerView das Hauptelement der Inhaltsansicht. Beispiele finden Sie unter UI-Komponenten mit NavigationUI aktualisieren.

Für ein einfaches Layout können Sie dieses FragmentContainerView-Element als untergeordnetes Element des Stamm-ViewGroup einfügen:

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

Wenn Sie unten auf den Tab Design klicken, sollte ein Diagramm wie die folgende angezeigt werden. Oben links in der Grafik sehen Sie unter Ziele einen Verweis auf die NavHost-Aktivität in Form von layout_name (resource_id).

Klicken Sie oben auf das Pluszeichen , um die Fragmente in die Grafik aufzunehmen.

Die Komponente „Navigation“ bezeichnet einzelne Bildschirme als Ziele. Ziele können Fragmente, Aktivitäten oder benutzerdefinierte Ziele sein. Sie können Ihrem Diagramm jeden Zieltyp hinzufügen. Aktivitätsziele werden jedoch als Terminalziele betrachtet, da Sie nach dem Aufrufen eines Aktivitätsziels in einem separaten Navigationshost und einer separaten Grafik arbeiten.

Die Komponente „Navigation“ bezeichnet die Art und Weise, wie Nutzer von einem Ziel zum anderen gelangen, als Aktionen. Aktionen können auch Übergangsanimationen und Pop-Verhalten beschreiben.

Fragmenttransaktionen entfernen

Wenn Sie jetzt die Navigationskomponente verwenden und unter derselben Aktivität zwischen fragmentbasierten Bildschirmen wechseln, können Sie FragmentManager-Interaktionen entfernen.

Wenn Ihre App mehrere Fragmente unter derselben Aktivität oder Navigation auf oberster Ebene verwendet, z. B. ein Leistenlayout oder die Navigation am unteren Rand, verwenden Sie wahrscheinlich FragmentManager und FragmentTransactions, um Fragmente im Hauptinhaltsbereich Ihrer UI hinzuzufügen oder zu ersetzen. Dies kann jetzt mithilfe der Navigationskomponente ersetzt und vereinfacht werden. Dazu werden Aktionen angegeben, um Ziele in der Grafik zu verknüpfen, und dann mit NavController navigieren.

Im Folgenden finden Sie einige Szenarien, denen Sie bei der Migration für jedes Szenario begegnen können.

Einzelne Aktivität, die mehrere Fragmente verwaltet

Wenn Sie eine einzelne Aktivität haben, mit der mehrere Fragmente verwaltet werden, könnte Ihr Aktivitätscode so aussehen:

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

Innerhalb des Quellziels rufen Sie möglicherweise eine Navigationsfunktion als Reaktion auf ein Ereignis auf, wie unten dargestellt:

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

Sie können dies ersetzen, indem Sie die Navigationsgrafik aktualisieren, um das Startziel und die Aktionen zum Verknüpfen der Ziele und Definieren von Argumenten festzulegen:

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

Anschließend können Sie Ihre Aktivitäten aktualisieren:

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

Für die Aktivität ist keine navigateToProductDetail()-Methode mehr erforderlich. Im nächsten Abschnitt aktualisieren wir ProductListFragment, damit NavController verwendet wird, um zum nächsten Bildschirm mit Produktdetails zu gelangen.

Argumente sicher übergeben

Die Navigationskomponente enthält ein Gradle-Plug-in namens Safe Args, das einfache Objekt- und Builder-Klassen für den typsicheren Zugriff auf Argumente generiert, die für Ziele und Aktionen angegeben sind.

Sobald das Plug-in angewendet wurde, generieren alle Argumente, die für ein Ziel in Ihrem Navigationsdiagramm definiert sind, vom Framework der Navigationskomponente eine Arguments-Klasse, die typsichere Argumente für das Ziel liefert. Wenn eine Aktion definiert wird, generiert das Plug-in eine Directions-Konfigurationsklasse, um dem NavController mitzuteilen, wie der Nutzer zum Ziel weitergeleitet werden soll. Wenn eine Aktion auf ein Ziel verweist, für das Argumente erforderlich sind, enthält die generierte Directions-Klasse Konstruktormethoden, für die diese Parameter erforderlich sind.

Verwenden Sie im Fragment NavController und die generierte Directions-Klasse, um typsichere Argumente für das Ziel bereitzustellen, wie im folgenden Beispiel gezeigt:

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

Navigation auf oberster Ebene

Wenn Ihre Anwendung eine DrawerLayout verwendet, enthält Ihre Aktivität möglicherweise eine Menge Konfigurationslogik, die das Öffnen und Schließen der Leiste sowie das Wechseln zu anderen Zielen verwaltet.

Die resultierende Aktivität könnte etwa so aussehen:

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

Nachdem Sie Ihrem Projekt die Navigationskomponente hinzugefügt und eine Navigationsgrafik erstellt haben, fügen Sie alle Inhaltsziele Ihrer Grafik hinzu, z. B. Startseite, Galerie, Diashow und Tools aus dem obigen Beispiel. Die id-Werte deiner Artikel auf der Speisekarte müssen den zugehörigen id-Zielwerten entsprechen:

<!-- 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>

Wenn Sie die id-Werte aus Ihrem Menü und der Grafik abgleichen, können Sie den NavController für diese Aktivität so einrichten, dass die Navigation basierend auf dem Menüpunkt automatisch gehandhabt wird. Das NavController übernimmt außerdem das Öffnen und Schließen von DrawerLayout und die entsprechende Verarbeitung der Auf- und Zurück-Schaltflächen.

Die MainActivity kann dann aktualisiert werden, um NavController mit Toolbar und NavigationView zu verbinden.

Hier ein Beispiel:

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

    }
}

Sie können dasselbe Verfahren sowohl mit der BottomNavigationView-basierten als auch der menübasierten Navigation verwenden. Weitere Beispiele finden Sie unter UI-Komponenten mit NavigationUI aktualisieren.

Aktivitätsziele hinzufügen

Sobald alle Bildschirme in Ihrer App für die Verwendung der Navigationskomponente eingerichtet sind und Sie FragmentTransactions nicht mehr für den Wechsel zwischen fragmentbasierten Zielen verwenden, besteht der nächste Schritt darin, startActivity-Aufrufe zu eliminieren.

Ermitteln Sie zuerst Stellen in Ihrer App, an denen es zwei separate Navigationsdiagramme gibt, und verwenden Sie startActivity, um zwischen ihnen zu wechseln.

Dieses Beispiel enthält zwei Diagramme (A und B) und einen startActivity()-Aufruf für den Übergang von A nach 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);

Ersetzen Sie diese als Nächstes durch ein Aktivitätsziel in Grafik A, das die Navigation zur Hostaktivität von Diagramm B darstellt. Wenn Sie Argumente haben, die an das Startziel von Grafik B übergeben werden sollen, können Sie sie in der Definition des Aktivitätsziels angeben.

Im folgenden Beispiel definiert Diagramm A ein Aktivitätsziel, das ein product_id-Argument zusammen mit einer Aktion annimmt. Grafik B enthält keine Änderungen.

Die XML-Darstellung der Grafiken A und B könnte wie folgt aussehen:

<!-- 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>

Sie können die Hostaktivität von Graph B mit denselben Mechanismen wie Fragmentziele aufrufen:

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

Argumente für das Aktivitätsziel an ein Startzielfragment übergeben

Wenn die Zielaktivität Extras erhält, können Sie diese wie im vorherigen Beispiel direkt als Argumente an das Startziel übergeben. Sie müssen die Navigationsgrafik Ihrer Hostaktivität jedoch manuell in der Methode onCreate() der Hostaktivität festlegen, damit Sie die Intent-Extras als Argumente an das Fragment übergeben können (siehe unten):

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

}

Die Daten können mithilfe der generierten Argumentklasse aus den Fragmentargumenten Bundle abgerufen werden, wie im folgenden Beispiel gezeigt:

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

Aktivitäten kombinieren

In Fällen, in denen mehrere Aktivitäten das gleiche Layout haben, lassen sich Navigationsdiagramme kombinieren, z. B. bei einer einfachen FrameLayout mit einem einzelnen Fragment. In den meisten Fällen reicht es aus, alle Elemente aus der einzelnen Navigationsgrafik zu kombinieren und alle Elemente des Aktivitätsziels zu Fragmentzielen zu aktualisieren.

Im folgenden Beispiel werden die Grafiken A und B aus dem vorherigen Abschnitt kombiniert:

Vor dem Kombinieren:

<!-- 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>

Nach dem Kombinieren:

<!-- 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>

Wenn Sie die Aktionsnamen während der Zusammenführung beibehalten, kann dieser Prozess nahtlos erfolgen, ohne dass Änderungen an Ihrer vorhandenen Codebasis vorgenommen werden müssen. Zum Beispiel bleibt navigateToProductDetail hier gleich. Der einzige Unterschied besteht darin, dass diese Aktion jetzt die Navigation zu einem Fragmentziel innerhalb derselben NavHost anstelle eines Aktivitätsziels darstellt:

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

Weitere Ressourcen

Weitere Informationen zur Navigation finden Sie in den folgenden Themen: