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 .
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)
A navegação no menu funcionou - não é um milagre ?!
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 :

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:
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"> <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 EditActivityEu 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 principalAdicionei 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çãoNeste exemplo,
ImportFragmentDirections é a classe safe-args gerada automaticamente.
val direction = ImportFragmentDirections.edit(123 ) navController.navigate(direction)
3. Obtenha o ID no fragmento class EditFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
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 .