Bonjour à tous! Je veux parler des fonctionnalités du travail de
Navigation Architecture Component , à cause desquelles j'ai eu une impression ambiguë de la bibliothèque.
Cet article n'est pas un guide étape par étape; il omet les détails de mise en œuvre pour se concentrer sur les points clés. Il existe de nombreux cas d'utilisation
similaires sur Internet (il existe également des traductions) - ils vous aideront à vous familiariser avec la bibliothèque. Aussi, avant de lire, je propose d'étudier la
documentation .
Je dois dire tout de suite que je considère certainement la bibliothèque utile et n'exclut pas la possibilité d'une mauvaise utilisation, mais j'ai probablement tout essayé avant d'écrire cet article.Voici donc les scénarios dans la mise en œuvre dont les attentes pour la fonctionnalité ne coïncidaient pas avec la réalité de la mise en œuvre:
- basculer entre les éléments de menu dans le tiroir de navigation
- découverte d'une nouvelle Activité avec son graphe de navigation
- passage de paramètres à startDestination
Basculer entre les éléments de menu
C'est l'une de ces fonctionnalités qui a influencé la décision d'utiliser le composant de navigation.
Il vous suffit de rendre l'identifiant de l'élément de menu identique
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"/>
et id d'écran (destination dans le graphique de navigation)
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>
alors vous devez associer le menu au contrôleur de navigation:
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)
La navigation dans le menu a fonctionné - n'est-ce pas un miracle?!
Faites attention au «hamburger» (icône de menu), lorsque vous basculez entre les éléments du menu, il change son état sur le bouton «retour». Ce comportement semblait
inhabituel (
familier - comme dans l'application Play Market) et, pendant un certain temps, j'ai essayé de comprendre ce qui n'allait pas.
C’est tout! Après avoir lu la documentation sur les principes de navigation (à savoir, les points
deux et
trois ), j'ai réalisé que le «hamburger» n'est affiché que pour
startDestination , ou plutôt de cette façon: le bouton de retour est affiché pour tout le monde sauf
startDestination . La situation peut être modifiée en appliquant diverses astuces dans l'abonnement (
addOnNavigatedListener () ) pour modifier la
destination , mais elles ne doivent même pas être décrites. Cela fonctionne comme ça, vous devez vous réconcilier.
Ouverture d'une nouvelle activité
L'activité peut agir en tant
qu'hôte de navigation et, en même temps, dans le graphique de navigation peut agir en tant que
destination . L'ouverture d'une activité sans graphique de navigation imbriqué fonctionne
comme prévu , c'est-à-dire un appel:
navController.navigate(R.id.editActivity)
effectuera la transition (comme dans le cas des fragments) et ouvrira l'activité demandée.
Il est beaucoup plus intéressant de considérer le cas où l'activité cible elle-même agit comme
hôte de navigation , c'est-à-dire l'option 2 de la
documentation :

À titre d'exemple, regardons une activité pour ajouter une note. Il
contiendra le fragment principal avec les
champs de saisie
EditFragment ; ce sera
startDestination dans le graphique de navigation. Supposons que lors de l'édition, nous devons joindre une photo, pour cela, nous irons dans
PhotoFragment pour obtenir une photo de l'appareil photo. Le graphique de navigation ressemblera à ceci:
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 n'est pas très différent de
MainActivity . La principale différence est qu'il n'y a pas de menu sur
EditActivity :
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) } }
L'activité s'ouvre, la navigation à l'intérieur fonctionne:
Encore une fois, faites attention au bouton de navigation dans la barre d'outils - sur le
EditFragment de départ, il n'y a pas de bouton "Retour à l'activité parent" (mais je voudrais). Du point de vue de la documentation, tout est légal ici: un nouveau graphe de navigation, une nouvelle valeur
startDestination , le bouton "Retour"
n'apparaît pas à
startDestination , la fin.
Pour ceux qui veulent revenir à leur
comportement habituel avec l'activité parentale , tout en conservant la fonctionnalité de basculement entre les fragments, je peux proposer cette approche
béquille :
1. Spécifiez l'activité parent dans le manifeste <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. Ajoutez un abonnement dans lequel nous remplacerons id startDestination 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) } }
Un abonnement est nécessaire pour que pour
NavigationUI.ActionBarOnNavigatedListener toutes les destinations ne soient pas startDestination . Ainsi,
NavigationUI.ActionBarOnNavigatedListener ne masquera pas le bouton de navigation (reportez-vous à la source pour plus de détails). Ajoutez à cela le traitement
onSupportNavigateUp () de manière régulière à
startDestination et obtenez ce que nous voulions.
Il vaut la peine de dire que la solution est loin d'être idéale, ne serait-ce que parce que c'est une intervention non évidente dans le comportement de la bibliothèque. Je pense que des problèmes peuvent survenir lors de l'utilisation de
liens profonds (je ne l'ai pas encore testé).
Passer des paramètres à startDestination
Le composant de navigation dispose d'un
mécanisme pour transmettre des paramètres d'une
destination à une autre. Il existe même un
outil pour assurer la sécurité des types grâce à la génération de code (pas mal).
Nous allons maintenant analyser le cas, à cause duquel je n'ai pas pu mettre un solide cinq à cette fonction.
Revenons à
EditActivity , un scénario assez familier lorsqu'une activité est utilisée pour créer et modifier des objets. Lorsque vous ouvrez un objet pour le modifier dans Activity, vous devez transférer, par exemple, l'ID de l'objet - faisons-le de manière régulière:
1. Ajoutez un paramètre au graphique pour EditActivityJ'ai ajouté le paramètre directement à l'élément racine du graphique (navigation), mais peut être ajouté au fragment cible. À partir de cela, seule la méthode d'obtention du paramètre changera.
<?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. Ajoutez des actions au graphique principalJ'ai ajouté des actions d'ajout et de modification à l'un des fragments, de sorte qu'elles ne seront disponibles qu'à partir de celui-ci.
<?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. Préparez les paramètres et demandez la transitionDans cet exemple,
ImportFragmentDirections est la classe Safe-
Args générée automatiquement.
val direction = ImportFragmentDirections.edit(123 ) navController.navigate(direction)
3. Obtenez l'id dans le fragment class EditFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
Vous avez
certes prêté attention aux fonctionnalités d'obtention des paramètres dans
EditFragment . Cela fonctionne car l'action d'édition (à partir du point 1) transmet des arguments à
EditActivity , et pour sa part, pour une raison quelconque, il est
avide de ne pas la transmettre au graphique (par exemple, en appelant
navController.graph.setDefaultArguments () ). Cette fonction peut être contournée en préparant manuellement le
contrôleur de navigation . Une façon est décrite sur
StackOwerflow .
La plus grande difficulté se posera peut-être lors de l'utilisation simultanée de
startDestination et de
destination habituelle. En d'autres
termes , lors de la transmission et de la transmission de paramètres à
startDestination à partir de toute autre
destination de ce graphique, le fragment devra déterminer indépendamment d'où extraire les paramètres: à partir d'arguments ou d'intention.extras. Il faut en tenir compte lors de la conception de transitions avec des paramètres de passage.
En résumé, je tiens à noter que je n'ai moi-même pas cessé d'utiliser la bibliothèque et, malgré les
inconvénients énumérés
de la fonctionnalité, je la trouve suffisamment utile pour être recommandée. J'espère vraiment que dans les prochaines versions, la situation changera, au moins avec le transfert des paramètres à
startDestination .
Merci de votre attention. Votre code de travail!
Les sources de l'article sont publiées sur
GitHub .