Componente de navegaci贸n de Android. Cosas simples que tienes que hacer t煤 mismo

Hola a todos! Quiero hablar sobre las caracter铆sticas en el trabajo del Componente de Arquitectura de Navegaci贸n , por lo que tuve una impresi贸n ambigua de la biblioteca.

Este art铆culo no es una gu铆a paso a paso; omite los detalles de implementaci贸n para centrarse en puntos clave. Hay muchos casos de uso similares en Internet (tambi茅n hay traducciones): le ayudar谩n a familiarizarse con la biblioteca. Adem谩s, antes de leer, propongo estudiar la documentaci贸n .

imagen

Debo decir de inmediato que definitivamente considero que la biblioteca es 煤til y no excluyo la posibilidad de mal uso, pero probablemente intent茅 todo antes de escribir este art铆culo.

Entonces, aqu铆 est谩n los escenarios en la implementaci贸n de los cuales las expectativas para la funcionalidad no coincidieron con la realidad en la implementaci贸n:

  • cambiar entre elementos de men煤 en el caj贸n de navegaci贸n
  • descubrimiento de una nueva actividad con su gr谩fico de navegaci贸n
  • pasar par谩metros a startDestination

Cambiar entre elementos del men煤


Esta es una de esas caracter铆sticas que influyeron en la decisi贸n de usar el componente de navegaci贸n.

Solo necesita hacer que la identificaci贸n del elemento del men煤 sea id茅ntica

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


e ID de pantalla (destino en el gr谩fico de navegaci贸n)

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> 


entonces necesita asociar el men煤 con el controlador de navegaci贸n:

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


La navegaci贸n en el men煤 ha funcionado, 驴no es un milagro?

Men煤 de navegaci贸n

Preste atenci贸n a la "hamburguesa" (icono de men煤), cuando cambia entre los elementos del men煤, cambia su estado al bot贸n "atr谩s". Este comportamiento parec铆a inusual ( familiar , como en la aplicaci贸n del mercado de juegos) y, por un tiempo, trat茅 de descubrir qu茅 hizo mal.

Eso es todo! Despu茅s de leer la documentaci贸n sobre los principios de navegaci贸n (a saber, los puntos dos y tres ), me di cuenta de que la "hamburguesa" se muestra solo para startDestination , o m谩s bien, de esta manera: el bot贸n de retroceso se muestra para todos excepto startDestination . La situaci贸n se puede cambiar aplicando varios trucos en la suscripci贸n ( addOnNavigatedListener () ) para cambiar el destino , pero ni siquiera se deben describir. Funciona as铆, debes llegar a un acuerdo.

Abrir una nueva actividad


La actividad puede actuar como un host de navegaci贸n y, al mismo tiempo, en el gr谩fico de navegaci贸n puede actuar como uno de los destinos . Abrir una actividad sin un gr谩fico de navegaci贸n anidado funciona como se esperaba , es decir, una llamada:

 navController.navigate(R.id.editActivity) 

realizar谩 la transici贸n (como en el caso de los fragmentos) y abrir谩 la Actividad solicitada.
Es mucho m谩s interesante considerar el caso cuando la Actividad objetivo en s铆 misma act煤a como un host de navegaci贸n , es decir, la opci贸n 2 de la documentaci贸n :

Esquema de navegaci贸n

Como ejemplo, veamos una Actividad para agregar una nota. Contendr谩 el fragmento principal con los campos de entrada EditFragment ; ser谩 startDestination en el gr谩fico de navegaci贸n. Supongamos que al editar necesitamos adjuntar una foto, para esto iremos a PhotoFragment para obtener una foto de la c谩mara. El gr谩fico de navegaci贸n se ver谩 as铆:

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 no es muy diferente de MainActivity . La principal diferencia es que no hay men煤 en 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) } } 


La actividad se abre, la navegaci贸n en su interior funciona:

Editar actividad de navegaci贸n

Nuevamente, preste atenci贸n al bot贸n de navegaci贸n en la barra de herramientas: en el EditFragment inicial no hay un bot贸n "Volver a la actividad principal" (pero me gustar铆a). Desde el punto de vista de la documentaci贸n, todo es legal aqu铆: un nuevo gr谩fico de navegaci贸n, un nuevo valor startDestination , el bot贸n "Atr谩s" no se muestra en startDestination , el final.

Para aquellos que desean volver a su comportamiento habitual con la actividad de los padres , mientras mantienen la funcionalidad para cambiar entre fragmentos, puedo ofrecer este enfoque de muleta :

1. Especifique la actividad principal en el manifiesto
 <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. Agregue una suscripci贸n en la que reemplazaremos 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) } } 


Es necesaria una suscripci贸n para que NavigationUI.ActionBarOnNavigatedListener no todos los destinos sean startDestination . Por lo tanto, NavigationUI.ActionBarOnNavigatedListener no ocultar谩 el bot贸n de navegaci贸n (consulte la fuente para obtener m谩s detalles). Agreguemos a esto el procesamiento onSupportNavigateUp () de manera regular en startDestination y obtenga lo que quer铆amos.

Vale la pena decir que la soluci贸n est谩 lejos de ser ideal, aunque solo sea porque es una intervenci贸n obvia en el comportamiento de la biblioteca. Creo que pueden surgir problemas al usar enlaces profundos (a煤n no lo he probado).

Pasando par谩metros para comenzar Destino


El componente de navegaci贸n tiene un mecanismo para pasar par谩metros de un destino a otro. Incluso hay una herramienta para garantizar la seguridad de los tipos mediante la generaci贸n de c贸digo (no est谩 mal).

Ahora analizaremos el caso, por lo que no pude poner un s贸lido cinco a este funcional.

Volvamos a EditActivity , un escenario bastante familiar cuando una Actividad se usa para crear y editar objetos. Cuando abre un objeto para editarlo en Activity, debe transferir, por ejemplo, la identificaci贸n del objeto; hag谩moslo de manera regular:

1. Agregue un par谩metro al gr谩fico para EditActivity
Agregu茅 el par谩metro directamente al elemento ra铆z del gr谩fico (navegaci贸n), pero se puede agregar al fragmento de destino. A partir de esto, solo cambiar谩 el m茅todo para obtener el par谩metro.

 <?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. Agregar acciones al gr谩fico principal
Agregu茅 agregar y editar acciones a uno de los fragmentos, por lo que estar谩n disponibles solo desde 茅l.

 <?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. Prepare los par谩metros y solicite la transici贸n.
En este ejemplo, ImportFragmentDirections es la clase safe-args generada autom谩ticamente.

 val direction = ImportFragmentDirections.edit(123 /* id  */) navController.navigate(direction) 


3. Obtenga la identificaci贸n en el fragmento
 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) } } 


Usted, por cierto, prest贸 atenci贸n a las caracter铆sticas de obtener par谩metros en EditFragment . Esto funciona porque la acci贸n de edici贸n (desde el punto 1) pasa argumentos a EditActivity , y por su parte, por alguna raz贸n, es codicioso que no lo pase al gr谩fico (por ejemplo, llamando a navController.graph.setDefaultArguments () ). Esta caracter铆stica se puede eludir preparando manualmente el controlador de navegaci贸n . Una forma se describe en StackOwerflow .

Quiz谩s la mayor dificultad surja cuando se usa simult谩neamente como startDestination y el destino habitual. Es decir, al pasar y pasar par谩metros a startDestination desde cualquier otro destino de este gr谩fico, el fragmento tendr谩 que determinar de forma independiente d贸nde extraer los par谩metros de: a partir de argumentos o de intent.extras. Esto debe tenerse en cuenta al dise帽ar transiciones con par谩metros de paso.



En resumen, quiero se帽alar que yo mismo no he dejado de usar la biblioteca y, a pesar de las desventajas enumeradas de la funci贸n, me parece lo suficientemente 煤til como para recomendar su uso. Realmente espero que en las pr贸ximas versiones la situaci贸n cambie, al menos con la transferencia de par谩metros para comenzarDestination .

Gracias por su atencion Tu c贸digo de trabajo!

Las fuentes del art铆culo se publican en GitHub .

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


All Articles