Android-Navigationskomponente. Einfache Dinge, die Sie selbst tun müssen

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.

Bild

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) //   ""  toolbar NavigationUI.setupWithNavController(toolbar, navController, drawer_layout) //      nav_view.setupWithNavController(navController) } //     override fun onSupportNavigateUp() = navController.navigateUp() } 


Die Navigation im Menü hat funktioniert - ist das nicht ein Wunder ?!

Menüführung

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 :

Navigationsschema

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:

Aktivitätsnavigation bearbeiten

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"> <!-- Parent activity meta-data to support 4.0 and lower --> <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 hinzu
Ich 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 hinzu
Ich 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 an
In diesem Beispiel ist ImportFragmentDirections die automatisch generierte Safe-Args-Klasse.

 val direction = ImportFragmentDirections.edit(123 /* id  */) 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 { //    ,  fragment     startDestination // val id = EditFragmentArgs.fromBundle(arguments) val id = EditFragmentArgs.fromBundle(requireActivity().intent.extras) return inflater.inflate(R.layout.fragment_edit, container, false) } } 


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 .

Source: https://habr.com/ru/post/de429152/


All Articles