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 .
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)
La navegaci贸n en el men煤 ha funcionado, 驴no es un milagro?
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 :

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:
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"> <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 EditActivityAgregu茅 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 principalAgregu茅 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 ) navController.navigate(direction)
3. Obtenga la identificaci贸n en el fragmento class EditFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
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 .