Componente de navegação Android. Coisas simples que você precisa fazer

Olá pessoal! Quero falar sobre os recursos do trabalho do Navigation Architecture Component , por causa dos quais tive uma impressão ambígua sobre a biblioteca.

Este artigo não é um guia passo a passo; omite os detalhes da implementação para se concentrar nos pontos principais. Existem muitos casos de uso semelhantes na Internet (também existem traduções) - eles ajudarão a se familiarizar com a biblioteca. Além disso, antes de ler, proponho estudar a documentação .

imagem

Devo dizer imediatamente que certamente considero a biblioteca útil e não excluo a possibilidade de uso indevido, mas provavelmente já tentei de tudo antes de escrever este artigo.

Portanto, aqui estão os cenários na implementação em que as expectativas para a funcionalidade não coincidem com a realidade na implementação:

  • alternando entre itens de menu na gaveta de navegação
  • descoberta de uma nova atividade com seu gráfico de navegação
  • passando parâmetros para startDestination

Alternar entre itens de menu


Esse é um desses recursos que influenciaram a decisão de usar o componente de navegação.

Você só precisa tornar o ID do item de menu idêntico

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 da tela (destino no gráfico de navegação)

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> 


então você precisa associar o menu ao controlador de navegação:

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


A navegação no menu funcionou - não é um milagre ?!

Navegação no menu

Preste atenção ao “hambúrguer” (ícone do menu); ao alternar entre os itens do menu, ele muda de estado para o botão “voltar”. Esse comportamento parecia incomum ( familiar - como no aplicativo de mercado de jogos) e, por um tempo, tentei descobrir o que havia de errado?

Isso é tudo! Depois de ler a documentação sobre os princípios de navegação (ou seja, pontos dois e três ), percebi que o “hambúrguer” é mostrado apenas para startDestination , ou melhor, desta forma: o botão voltar é mostrado para todos, exceto startDestination . A situação pode ser alterada aplicando vários truques na assinatura ( addOnNavigatedListener () ) para alterar o destino , mas eles nem devem ser descritos. Funciona assim, você precisa chegar a um acordo.

Abrindo uma nova atividade


A atividade pode atuar como um host de navegação e, ao mesmo tempo, no gráfico de navegação, como um dos destinos . A abertura de uma atividade sem um gráfico de navegação aninhado funciona conforme o esperado , ou seja, uma chamada:

 navController.navigate(R.id.editActivity) 

realizará a transição (como no caso de fragmentos) e abrirá a atividade solicitada.
É muito mais interessante considerar o caso em que a Atividade de destino em si atua como um host de navegação , ou seja, a opção 2 da documentação :

Esquema de navegação

Como exemplo, vejamos uma Atividade para adicionar uma nota. Ele conterá o fragmento principal com os campos de entrada EditFragment e será startDestination no gráfico de navegação. Vamos supor que, ao editar, precisamos anexar uma foto, para isso iremos ao PhotoFragment para obter uma foto da câmera. O gráfico de navegação ficará assim:

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ão é muito diferente de MainActivity . A principal diferença é que não há menu no 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) } } 


A atividade é aberta, a navegação dentro dela funciona:

Editar navegação de atividade

Novamente, preste atenção ao botão de navegação na barra de ferramentas - no EditFragment inicial, não há botão "Voltar à atividade pai" (mas eu gostaria). Do ponto de vista da documentação, tudo é legal aqui: um novo gráfico de navegação, um novo valor startDestination , o botão "Voltar" não é mostrado em startDestination , no final.

Para aqueles que desejam retornar ao seu comportamento habitual com a atividade dos pais , mantendo a funcionalidade para alternar entre fragmentos, posso oferecer esta abordagem de muleta :

1. Especifique a atividade pai no manifesto
 <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. Adicione uma assinatura na qual substituiremos o 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) } } 


É necessária uma assinatura para que, para NavigationUI.ActionBarOnNavigatedListener, todos os destinos não sejam startDestination . Portanto, NavigationUI.ActionBarOnNavigatedListener não ocultará o botão de navegação (consulte a fonte para obter detalhes). Adicione a isso o processamento onSupportNavigateUp () regularmente em startDestination e obtenha o que queríamos.

Vale dizer que a solução está longe de ser ideal, apenas porque é uma intervenção não óbvia no comportamento da biblioteca. Acredito que podem surgir problemas ao usar links diretos (ainda não o testei).

Passando parâmetros para startDestination


O componente de navegação possui um mecanismo para passar parâmetros de um destino para outro. Existe até uma ferramenta para garantir a segurança do tipo através da geração de código (não é ruim).

Agora vamos analisar o caso, pelo qual não pude colocar cinco sólidos nessa funcionalidade.

Voltemos ao EditActivity , um cenário bastante familiar quando uma atividade é usada para criar e editar objetos. Ao abrir um objeto para edição no Activity, você precisa transferir, por exemplo, o ID do objeto - vamos fazê-lo de maneira regular:

1. Adicione um parâmetro ao gráfico para EditActivity
Eu adicionei o parâmetro diretamente ao elemento raiz do gráfico (navegação), mas pode ser adicionado ao fragmento de destino. A partir disso, apenas o método de obtenção do parâmetro será alterado.

 <?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. Adicione ações ao gráfico principal
Adicionei ações de adição e edição a um dos fragmentos, para que estejam disponíveis apenas a partir dele.

 <?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 os parâmetros e solicite a transição
Neste exemplo, ImportFragmentDirections é a classe safe-args gerada automaticamente.

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


3. Obtenha o ID no 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) } } 


Você, com certeza, prestou atenção aos recursos para obter parâmetros no EditFragment . Isso funciona porque a ação de edição (do ponto 1) passa argumentos para EditActivity e, por sua parte, por alguma razão, é ganancioso que não o passe para o gráfico (por exemplo, chamando navController.graph.setDefaultArguments () ). Este recurso pode ser contornado preparando manualmente o controlador de navegação . Uma maneira é descrita no StackOwerflow .

Talvez a maior dificuldade surja quando usada simultaneamente como startDestination e o destino usual. Ou seja, ao passar e passar parâmetros para startDestination de qualquer outro destino desse gráfico, o fragmento precisará determinar independentemente de onde extrair os parâmetros: de argumentos ou de intent.extras. Isso deve ser lembrado ao projetar transições com parâmetros de passagem.



Resumindo, quero observar que eu mesmo não parei de usar a biblioteca e, apesar das desvantagens listadas no recurso, acho útil o suficiente para recomendar o uso. Eu realmente espero que nos próximos lançamentos a situação mude, pelo menos com a transferência de parâmetros para startDestination .

Obrigado pela atenção. Seu código de trabalho!

As fontes do artigo são postadas no GitHub .

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


All Articles