Hallo allerseits! Ich möchte über die Funktionen in der Arbeit von
Navigation Architecture Component sprechen, aufgrund derer ich einen mehrdeutigen Eindruck von der Bibliothek hatte.
Dieser Artikel ist keine schrittweise Anleitung, sondern enthält keine Implementierungsdetails, um sich auf wichtige Punkte zu konzentrieren. Es gibt viele
ähnliche Anwendungsfälle im Internet (es gibt auch Übersetzungen) - sie helfen Ihnen, sich mit der Bibliothek vertraut zu machen. Außerdem schlage ich vor dem Lesen vor, die
Dokumentation zu studieren.
Ich muss sofort sagen, dass ich die Bibliothek definitiv für nützlich halte und die Möglichkeit eines Missbrauchs nicht ausschließe, aber ich habe wahrscheinlich alles versucht, bevor ich diesen Artikel geschrieben habe.Hier sind die Szenarien, in deren Implementierung die Erwartungen an die Funktionalität nicht mit der Realität in der Implementierung übereinstimmten:
- Wechseln zwischen Menüelementen in der Navigationsleiste
- Entdeckung einer neuen Aktivität mit ihrem Navigationsdiagramm
- Übergabe von Parametern an startDestination
Zwischen Menüpunkten wechseln
Dies ist eine dieser Funktionen, die die Entscheidung für die Verwendung der Navigationskomponente beeinflusst haben.
Sie müssen nur die Menüelement-ID identisch machen
activity_main_drawer.xml<?xml version="1.0" encoding="utf-8"?> <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/importFragment" android:icon="@drawable/ic_menu_camera" android:title="Import"/> <item android:id="@+id/galleryFragment" android:icon="@drawable/ic_menu_gallery" android:title="Gallery"/> <item android:id="@+id/slideshowFragment" android:icon="@drawable/ic_menu_slideshow" android:title="Slideshow"/>
und Bildschirm-ID (Ziel im Navigationsdiagramm)
mobile_navigation.xml <?xml version="1.0" encoding="utf-8"?> <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/mobile_navigation" app:startDestination="@id/importFragment"> <fragment android:id="@+id/importFragment" android:name="com.xiii.navigationapplication.ImportFragment" android:label="fragment_import" tools:layout="@layout/fragment_import"/> <fragment android:id="@+id/galleryFragment" android:name="com.xiii.navigationapplication.GalleryFragment" android:label="fragment_gallery" tools:layout="@layout/fragment_gallery"/> <fragment android:id="@+id/slideshowFragment" android:name="com.xiii.navigationapplication.SlideshowFragment" android:label="fragment_slideshow" tools:layout="@layout/fragment_slideshow"/> </navigation>
Dann müssen Sie das Menü dem Navigationscontroller zuordnen:
MainActivity.kt class MainActivity : AppCompatActivity() { private val navController by lazy(LazyThreadSafetyMode.NONE) { Navigation.findNavController(this, R.id.nav_host_fragment) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar)
Die Navigation im Menü hat funktioniert - ist das nicht ein Wunder ?!
Achten Sie auf den „Hamburger“ (Menüsymbol). Wenn Sie zwischen den Menüelementen wechseln, ändert sich sein Status in die Schaltfläche „Zurück“. Dieses Verhalten schien
ungewöhnlich (
vertraut - wie in der Spielmarktanwendung) und für eine Weile versuchte ich herauszufinden, was falsch lief?
Das ist alles! Nachdem ich die Dokumentation zu den Prinzipien der Navigation gelesen hatte (nämlich Punkte
zwei und
drei ), stellte ich fest, dass der „Hamburger“ nur für
startDestination angezeigt wird , oder besser gesagt: Die Schaltfläche Zurück wird für alle außer
startDestination angezeigt . Die Situation kann geändert werden, indem verschiedene Tricks im Abonnement (
addOnNavigatedListener () )
angewendet werden , um das
Ziel zu ändern. Sie sollten jedoch nicht einmal beschrieben werden. Es funktioniert so, man muss sich abfinden.
Öffnen einer neuen Aktivität
Die Aktivität kann als
Navigationshost fungieren und gleichzeitig im Navigationsdiagramm als eines der
Ziele fungieren. Das Öffnen einer Aktivität ohne verschachteltes Navigationsdiagramm funktioniert
wie erwartet , d. H. Ein Aufruf:
navController.navigate(R.id.editActivity)
führt den Übergang durch (wie im Fall von Fragmenten) und öffnet die angeforderte Aktivität.
Es ist viel interessanter, den Fall zu betrachten, in dem die Zielaktivität selbst als
Navigationshost fungiert, dh Option 2 aus der
Dokumentation :

Schauen wir uns als Beispiel eine Aktivität an, um eine Notiz hinzuzufügen. Es
enthält das Hauptfragment mit
den Eingabefeldern von
EditFragment und im Navigationsdiagramm
startDestination . Nehmen wir an, dass wir beim Bearbeiten ein Foto anhängen müssen. Dazu gehen wir zu
PhotoFragment , um ein Bild von der Kamera zu erhalten. Das Navigationsdiagramm sieht folgendermaßen aus:
edit_navigation.xml <?xml version="1.0" encoding="utf-8"?> <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/edit_navigation" app:startDestination="@id/editFragment"> <fragment android:id="@+id/editFragment" android:name="com.xiii.navigationapplication.ui.edit.EditFragment" android:label="fragment_edit" tools:layout="@layout/fragment_edit"> <action android:id="@+id/action_editFragment_to_photoFragment" app:destination="@id/photoFragment"/> </fragment> <fragment android:id="@+id/photoFragment" android:name="com.xiii.navigationapplication.ui.edit.PhotoFragment" android:label="fragment_photo" tools:layout="@layout/fragment_photo"/> </navigation>
EditActivity unterscheidet sich nicht wesentlich von
MainActivity . Der Hauptunterschied besteht darin, dass
EditActivity kein Menü
enthält :
EditActivity.kt class EditActivity : AppCompatActivity() { private val navController by lazy(LazyThreadSafetyMode.NONE) { Navigation.findNavController(this, R.id.nav_host_fragment) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit) setSupportActionBar(toolbar) // "" toolbar NavigationUI.setupWithNavController(toolbar, navController) } override fun onSupportNavigateUp() = navController.navigateUp() fun takePhoto(view: View) { navController.navigate(R.id.action_editFragment_to_photoFragment) } }
Die Aktivität wird geöffnet, die Navigation funktioniert:
Achten Sie auch hier auf die Navigationsschaltfläche in der Symbolleiste - beim Starten von
EditFragment gibt es keine Schaltfläche "Zurück zur übergeordneten Aktivität" (aber ich möchte). Aus dokumentatorischer Sicht ist hier alles legal: Ein neues Navigationsdiagramm, ein neuer
startDestination- Wert, die Schaltfläche "Zurück" wird bei
startDestination , dem Ende, nicht angezeigt.
Für diejenigen, die zu ihrem
gewohnten Verhalten mit übergeordneten Aktivitäten zurückkehren und gleichzeitig die Funktionalität zum Wechseln zwischen Fragmenten beibehalten möchten, kann ich diesen
Krückenansatz anbieten:
1. Geben Sie die übergeordnete Aktivität im Manifest an <activity android:name=".EditActivity" android:parentActivityName=".MainActivity" android:theme="@style/AppTheme.NoActionBar"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity" /> </activity>
2. Fügen Sie ein Abonnement hinzu, in dem wir die ID startDestination ersetzen class EditActivity : AppCompatActivity() { private val navController by lazy(LazyThreadSafetyMode.NONE) { Navigation.findNavController(this, R.id.nav_host_fragment) } private var isStartDestination = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit) setSupportActionBar(toolbar) val startDestinationId = navController.graph.startDestination // id, NavigationUI.ActionBarOnNavigatedListener // destination startDestination navController.addOnNavigatedListener { controller, destination -> isStartDestination = destination.id == startDestinationId // R.id.fake_start_destination id controller.graph.startDestination = if (isStartDestination) R.id.fake_start_destination else startDestinationId } // "" toolbar NavigationUI.setupActionBarWithNavController(this, navController) } override fun onSupportNavigateUp(): Boolean { // startDestination Navigation Component return if (isStartDestination) super.onSupportNavigateUp() else navController.navigateUp() } fun takePhoto(view: View) { navController.navigate(R.id.action_editFragment_to_photoFragment) } }
Ein Abonnement ist erforderlich, damit für
NavigationUI.ActionBarOnNavigatedListener nicht alle Ziele startDestination sind . Daher verbirgt NavigationUI.ActionBarOnNavigatedListener die Navigationsschaltfläche nicht (Einzelheiten finden Sie in der Quelle). Fügen Sie dazu die Verarbeitung von
onSupportNavigateUp () regelmäßig bei
startDestination hinzu und erhalten Sie, was wir wollten.
Es ist erwähnenswert, dass die Lösung alles andere als ideal ist, schon allein deshalb, weil es sich um einen nicht offensichtlichen Eingriff in das Verhalten der Bibliothek handelt. Ich glaube, dass bei der Verwendung von
Deep Links Probleme auftreten können (ich habe es noch nicht getestet).
Übergabe von Parametern an startDestination
Die Navigationskomponente verfügt über einen
Mechanismus zum Übergeben von Parametern von einem
Ziel zu einem anderen. Es gibt sogar ein
Tool, um die Typensicherheit durch Codegenerierung
zu gewährleisten (nicht schlecht).
Jetzt werden wir den Fall analysieren, aufgrund dessen ich dieser Funktion keine solide Fünf geben konnte.
Kehren wir zu
EditActivity zurück , einem ziemlich vertrauten Szenario, in dem eine Aktivität zum Erstellen und Bearbeiten von Objekten verwendet wird. Wenn Sie ein Objekt zum Bearbeiten in Aktivität öffnen, müssen Sie beispielsweise die ID des Objekts übertragen. Führen Sie dies regelmäßig aus:
1. Fügen Sie dem Diagramm einen Parameter für EditActivity hinzuIch habe den Parameter direkt zum Stammelement des Diagramms hinzugefügt (Navigation), kann aber zum Zielfragment hinzugefügt werden. Daraus ändert sich nur die Methode zum Abrufen des Parameters.
<?xml version="1.0" encoding="utf-8"?> <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/edit_navigation" app:startDestination="@id/editFragment"> <argument android:name="id" app:argType="integer"/> <fragment android:id="@+id/editFragment" android:name="com.xiii.navigationapplication.ui.edit.EditFragment" android:label="fragment_edit" tools:layout="@layout/fragment_edit"> <action android:id="@+id/action_editFragment_to_photoFragment" app:destination="@id/photoFragment"/> </fragment> <fragment android:id="@+id/photoFragment" android:name="com.xiii.navigationapplication.ui.edit.PhotoFragment" android:label="fragment_photo" tools:layout="@layout/fragment_photo"/> </navigation>
2. Fügen Sie dem Hauptdiagramm Aktionen hinzuIch habe einem der Fragmente Aktionen zum Hinzufügen und Bearbeiten hinzugefügt, damit sie nur dort verfügbar sind.
<?xml version="1.0" encoding="utf-8"?> <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/mobile_navigation" app:startDestination="@id/importFragment"> <fragment android:id="@+id/importFragment" android:name="com.xiii.navigationapplication.ImportFragment" android:label="fragment_import" tools:layout="@layout/fragment_import"> <action android:id="@+id/add" app:destination="@id/editActivity"> <argument android:name="id" app:argType="integer" android:defaultValue="0"/> </action> <action android:id="@+id/edit" app:destination="@id/editActivity"> <argument android:name="id" app:argType="integer"/> </action> </fragment> <fragment android:id="@+id/galleryFragment" android:name="com.xiii.navigationapplication.GalleryFragment" android:label="fragment_gallery" tools:layout="@layout/fragment_gallery"/> <fragment android:id="@+id/slideshowFragment" android:name="com.xiii.navigationapplication.SlideshowFragment" android:label="fragment_slideshow" tools:layout="@layout/fragment_slideshow"/> <activity android:id="@+id/editActivity" android:name="com.xiii.navigationapplication.EditActivity" android:label="activity_edit" tools:layout="@layout/activity_edit"/> </navigation>
3. Bereiten Sie die Parameter vor und fordern Sie den Übergang anIn diesem Beispiel ist
ImportFragmentDirections die automatisch generierte Safe-Args-Klasse.
val direction = ImportFragmentDirections.edit(123 ) navController.navigate(direction)
3. Holen Sie sich die ID in das Fragment class EditFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
Sie haben mit
Sicherheit auf die Funktionen zum
Abrufen von Parametern in
EditFragment geachtet . Dies funktioniert, weil die Bearbeitungsaktion (ab Punkt 1) Argumente an
EditActivity übergibt und es aus irgendeinem Grund
gierig ist ,
sie nicht an das Diagramm zu übergeben (z. B. durch Aufrufen von
navController.graph.setDefaultArguments () ). Diese Funktion kann umgangen werden, indem der
Navigationscontroller manuell vorbereitet
wird . Eine Möglichkeit wird in
StackOwerflow beschrieben .
Die vielleicht größte Schwierigkeit
ergibt sich, wenn sie gleichzeitig als
startDestination und als übliches
Ziel verwendet wird . Das heißt, wenn Parameter von einem anderen
Ziel dieses Diagramms an
startDestination übergeben und übergeben werden,
muss das Fragment unabhängig bestimmen, wo die Parameter extrahiert werden sollen: aus Argumenten oder aus intent.extras. Dies muss beim Entwerfen von Übergängen mit übergebenen Parametern berücksichtigt werden.
Zusammenfassend möchte ich darauf hinweisen, dass ich selbst die Nutzung der Bibliothek nicht eingestellt habe und sie trotz der aufgeführten
Nachteile der Funktion nützlich genug finde, um sie für die Verwendung zu empfehlen. Ich hoffe wirklich, dass sich die Situation in den nächsten Releases ändern wird, zumindest mit der Übertragung von Parametern an
startDestination .
Danke für die Aufmerksamkeit. Dein Arbeitscode!
Quellen für den Artikel sind auf
GitHub veröffentlicht .