Die Navigation ist die Nutzerinteraktion mit der Benutzeroberfläche einer Anwendung, um auf Inhaltsziele zuzugreifen. Die Navigationsprinzipien von Android bieten Richtlinien, mit denen Sie eine einheitliche, intuitive App-Navigation erstellen können.
Responsive/adaptive Benutzeroberflächen bieten responsive Inhaltsziele und enthalten häufig verschiedene Arten von Navigationselementen, die auf Änderungen der Bildschirmgröße reagieren. Beispiele hierfür sind eine Navigationsleiste unten auf kleinen Bildschirmen, eine Navigationsleiste auf mittelgroßen Bildschirmen oder ein dauerhafter Navigationsschubfach auf großen Bildschirmen. Responsive/adaptive Benutzeroberflächen sollten jedoch weiterhin den Navigationsprinzipien entsprechen.
Die Navigationskomponente von Jetpack implementiert die Prinzipien der Navigation und erleichtert die Entwicklung von Apps mit responsiven/adaptiven UIs.
Responsive UI-Navigation
Die Größe des Displayfensters, das von einer App belegt wird, wirkt sich auf Ergonomie und Nutzerfreundlichkeit aus. Mit Fenstergrößenklassen können Sie geeignete Navigationselemente (z. B. Navigationsleisten, ‑leisten oder ‑schubladen) festlegen und an der Stelle platzieren, an der sie für die Nutzer am besten zugänglich sind. Gemäß den Layoutrichtlinien für Material Design nehmen Navigationselemente einen festen Platz am oberen Displayrand ein und können zum unteren Rand verschoben werden, wenn die Breite der App kompakt ist. Die Auswahl der Navigationselemente hängt weitgehend von der Größe des App-Fensters und der Anzahl der Elemente ab, die das Element enthalten muss.
Fenstergrößenklasse | Einige Elemente | Viele Artikel |
---|---|---|
Kompaktbreite | Navigationsleiste unten | Navigationsleiste (oberer oder unterer Rand) |
mittlere Breite | Navigationsstreifen | Navigationsleiste (vorne) |
Breite nach Expansion | Navigationsstreifen | dauerhafte Navigationsleiste (vorne) |
Layout-Ressourcendateien können durch Unterbrechungen der Fenstergrößenklasse qualifiziert werden, um für unterschiedliche Bildschirmgrößen unterschiedliche Navigationselemente zu verwenden.
<!-- res/layout/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
... />
<!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- res/layout-w600dp/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.navigationrail.NavigationRailView
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
... />
<!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- res/layout-w1240dp/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.navigation.NavigationView
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
... />
<!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>
Ziele für responsive Inhalte
Bei einer responsiven Benutzeroberfläche passt sich das Layout der einzelnen Inhaltsziele an Änderungen der Fenstergröße an. In Ihrer App können Sie den Layoutabstand anpassen, Elemente neu positionieren, Inhalte hinzufügen oder entfernen oder UI-Elemente ändern, einschließlich Navigationselementen.
Wenn jedes einzelne Ziel Ereignisse zum Ändern der Größe verarbeitet, sind die Änderungen auf die Benutzeroberfläche beschränkt. Der Rest des App-Status, einschließlich der Navigation, bleibt davon unberührt.
Die Navigation sollte nicht als Nebeneffekt von Änderungen der Fenstergröße erfolgen. Erstellen Sie keine Inhaltsziele nur, um unterschiedliche Fenstergrößen zu berücksichtigen. Erstellen Sie beispielsweise keine unterschiedlichen Inhaltsziele für die verschiedenen Bildschirme eines faltbaren Geräts.
Das Aufrufen von Inhaltszielen als Nebeneffekt von Änderungen der Fenstergröße hat folgende Probleme:
- Das alte Ziel (für die vorherige Fenstergröße) ist möglicherweise kurz sichtbar, bevor die Navigation zum neuen Ziel erfolgt.
- Um die Reversibilität beizubehalten (z. B. wenn ein Gerät zusammengeklappt und wieder aufgeklappt wird), ist die Navigation für jede Fenstergröße erforderlich.
- Es kann schwierig sein, den Anwendungsstatus zwischen Zielen beizubehalten, da der Status beim Pop-out des Backstacks zerstört werden kann.
Außerdem ist Ihre App möglicherweise nicht einmal im Vordergrund, wenn sich die Fenstergröße ändert. Das Layout Ihrer App benötigt möglicherweise mehr Platz als die App im Vordergrund. Wenn der Nutzer zu Ihrer App zurückkehrt, haben sich Ausrichtung und Fenstergröße möglicherweise geändert.
Wenn für Ihre App je nach Fenstergröße eindeutige Inhaltsziele erforderlich sind, sollten Sie die entsprechenden Ziele in einem einzigen Ziel kombinieren, das alternative, adaptive Layouts enthält.
Inhaltsziele mit alternativen Layouts
Im Rahmen eines responsiven/adaptiven Designs kann ein einzelnes Navigationsziel je nach Größe des App-Fensters alternative Layouts haben. Jedes Layout nimmt das gesamte Fenster ein, aber für unterschiedliche Fenstergrößen werden unterschiedliche Layouts angezeigt (adaptives Design).
Ein kanonisches Beispiel ist die Listendetailansicht. Bei kompakten Fenstergrößen zeigt Ihre App ein Inhaltslayout für die Liste und eins für die Details an. Wenn Sie die Detailansicht der Liste aufrufen, wird zuerst nur das Listenlayout angezeigt. Wenn ein Listenelement ausgewählt ist, wird in Ihrer App das Detaillayout angezeigt und ersetzt die Liste. Wenn das Zurück-Steuerelement ausgewählt ist, wird das Listenlayout angezeigt und ersetzt die Details. Bei maximierter Fenstergröße werden die Listen- und Detaillayouts jedoch nebeneinander angezeigt.
Mit SlidingPaneLayout
können Sie ein einzelnes Navigationsziel erstellen, auf dem auf großen Bildschirmen zwei Inhaltsbereiche nebeneinander angezeigt werden, auf kleinen Bildschirmen wie z. B. auf herkömmlichen Smartphones jedoch jeweils nur ein Bereich.
<!-- Single destination for list and detail. -->
<navigation ...>
<!-- Fragment that implements SlidingPaneLayout. -->
<fragment
android:id="@+id/article_two_pane"
android:name="com.example.app.ListDetailTwoPaneFragment" />
<!-- Other destinations... -->
</navigation>
Weitere Informationen zum Implementieren eines Listen-/Detaillayouts mit SlidingPaneLayout
finden Sie unter Layout mit zwei Ansichten erstellen.
Ein Navigationsdiagramm
Verwenden Sie ein einzelnes Navigationsdiagramm mit einem responsiven Layout für jedes Inhaltsziel, um eine einheitliche Nutzererfahrung auf allen Geräten und Fenstergrößen zu ermöglichen.
Wenn Sie für jede Fenstergrößenklasse ein anderes Navigationsdiagramm verwenden, müssen Sie jedes Mal, wenn die App von einer Größenklasse in eine andere wechselt, das aktuelle Ziel des Nutzers in den anderen Diagrammen ermitteln, einen Backstack erstellen und Statusinformationen abgleichen, die sich zwischen den Diagrammen unterscheiden.
Verschachtelter Navigationshost
Ihre App kann ein Ziel für Inhalte enthalten, das selbst Ziele für Inhalte hat. In einem Listendetaillayout könnte der Bereich für Artikeldetails beispielsweise UI-Elemente enthalten, über die zu Inhalten gelangt wird, die die Artikeldetails ersetzen.
Wenn Sie diese Art der Navigationsleiste implementieren möchten, machen Sie den Detailbereich zu einem verschachtelten Navigationshost mit einem eigenen Navigationsgraphen, in dem die Ziele angegeben sind, auf die über den Detailbereich zugegriffen wird:
<!-- layout/two_pane_fragment.xml -->
<androidx.slidingpanelayout.widget.SlidingPaneLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sliding_pane_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_pane"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"/>
<!-- Detail pane is a nested navigation host. Its graph is not connected
to the main graph that contains the two_pane_fragment destination. -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/detail_pane"
android:layout_width="300dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/detail_pane_nav_graph" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
Das unterscheidet sich von einem verschachtelten Navigationsdiagramm, da das Navigationsdiagramm des verschachtelten NavHost
nicht mit dem Hauptnavigationsdiagramm verbunden ist. Das bedeutet, dass Sie nicht direkt von Zielen in einem Diagramm zu Zielen im anderen wechseln können.
Weitere Informationen finden Sie unter Verschachtelte Navigationsgrafiken.
Beibehaltener Status
Damit responsive Inhalte angezeigt werden können, muss Ihre App ihren Status beibehalten, wenn das Gerät gedreht oder zugeklappt wird oder die Größe des App-Fensters angepasst wird. Durch solche Konfigurationsänderungen werden standardmäßig die Aktivitäten, Fragmente und die Ansichtshierarchie der App neu erstellt. Wir empfehlen, den UI-Status mit einem ViewModel
zu speichern, da dieser bei Konfigurationsänderungen erhalten bleibt. (Siehe UI-Status speichern .)
Größenänderungen sollten reversibel sein, z. B. wenn der Nutzer das Gerät dreht und dann wieder zurückdreht.
Responsive/adaptive Layouts können bei unterschiedlichen Fenstergrößen unterschiedliche Inhalte anzeigen. Daher müssen in responsiven Layouts häufig zusätzliche Status im Zusammenhang mit Inhalten gespeichert werden, auch wenn der Status nicht für die aktuelle Fenstergröße gilt. Ein Layout bietet beispielsweise nur bei größeren Fensterbreiten Platz für ein zusätzliches scrollbares Widget. Wenn die Fensterbreite durch ein Größenänderungsereignis zu klein wird, wird das Widget ausgeblendet. Wenn die App wieder ihre vorherige Größe hat, wird das Scroll-Widget wieder sichtbar und die ursprüngliche Scrollposition sollte wiederhergestellt werden.
ViewModel-Bereiche
Im Entwicklerhandbuch Zur Navigationskomponente migrieren wird eine Architektur mit einer einzelnen Aktivität beschrieben, bei der Ziele als Fragmente und ihre Datenmodelle mit ViewModel
implementiert werden.
Ein ViewModel
ist immer auf einen Lebenszyklus beschränkt. Wenn dieser Lebenszyklus dauerhaft endet, wird der ViewModel
gelöscht und kann verworfen werden. Der Lebenszyklus, auf den sich die ViewModel
bezieht, und damit, wie weitreichend die ViewModel
freigegeben werden kann, hängt vom Property-Delegierten ab, der zum Abrufen der ViewModel
verwendet wird.
Im einfachsten Fall ist jedes Navigationsziel ein einzelnes Fragment mit einem vollständig isolierten UI-Status. Daher kann jedes Fragment den Property-Delegierten viewModels()
verwenden, um einen ViewModel
zu erhalten, der auf dieses Fragment beschränkt ist.
Wenn Sie den UI-Status zwischen Fragmenten teilen möchten, beschränken Sie die ViewModel
auf die Aktivität, indem Sie in den Fragmenten activityViewModels()
aufrufen. Das Äquivalent für Activity
ist einfach viewModels()
. So können die Aktivität und alle daran angehängten Fragmente die ViewModel
-Instanz teilen.
Bei einer Architektur mit einer einzelnen Aktivität bleibt dieser ViewModel
-Bereich jedoch so lange bestehen wie die App. Daher bleibt ViewModel
im Arbeitsspeicher, auch wenn keine Fragmente ihn verwenden.
Angenommen, Ihr Navigationsgraph enthält eine Sequenz von Fragmentzielen, die einen Bezahlvorgang darstellen, und der aktuelle Status für den gesamten Bezahlvorgang befindet sich in einem ViewModel
, das für alle Fragmente gemeinsam genutzt wird. Wenn Sie den Umfang der ViewModel
auf die Aktivität beschränken, ist das nicht nur zu weit gefasst, sondern es führt auch zu einem weiteren Problem: Wenn der Nutzer den Bezahlvorgang für eine Bestellung durchläuft und ihn dann noch einmal für eine zweite Bestellung durchläuft, wird für beide Bestellungen dieselbe Instanz der ViewModel
für den Bezahlvorgang verwendet. Vor dem Bezahlvorgang für die zweite Bestellung müssen Sie die Daten aus der ersten Bestellung manuell löschen. Fehler können für den Nutzer kostspielig sein.
Beschränken Sie die ViewModel
stattdessen auf ein Navigationsdiagramm im aktuellen NavController
. Erstellen Sie ein verschachteltes Navigationsdiagramm, um die Ziele zu kapseln, die Teil des Bezahlvorgangs sind. Verwenden Sie dann in jedem dieser Fragmentziele den Property-Delegierten navGraphViewModels()
und übergeben Sie die ID des Navigationsgraphs, um die freigegebene ViewModel
zu erhalten. So wird sichergestellt, dass die entsprechende Instanz der ViewModel
verworfen und nicht für den nächsten Bezahlvorgang verwendet wird, sobald der Nutzer den Bezahlvorgang beendet und der verschachtelte Navigationsgraph nicht mehr in den Geltungsbereich fällt.
Aufgabenstellung | Property-Delegierter | ViewModel kann mit folgenden Personen geteilt werden: |
---|---|---|
Fragment | Fragment.viewModels() |
Nur Fragment |
Aktivität | Activity.viewModels() oder Fragment.activityViewModels() |
Aktivität und alle zugehörigen Fragmente |
Navigationsgraph | Fragment.navGraphViewModels() |
Alle Fragmente im selben Navigationsgraphen |
Wenn Sie einen verschachtelten Navigationshost verwenden (siehe Abschnitt Verschachtelter Navigationshost), können Ziele in diesem Host keine ViewModel
-Instanzen mit Zielen außerhalb des Hosts teilen, wenn Sie navGraphViewModels()
verwenden, da die Graphen nicht verbunden sind. In diesem Fall können Sie stattdessen den Aktivitätsbereich verwenden.