Navigation. NavigationUI

Спочатку зроблю невелике доповнення до минулих уроків. У них уся робота з NavController велася в Activity, в якому знаходився контейнер NavHostFragment.

Код отримання контролера в Activity має такий вигляд:

navController = Navigation.findNavController(this, R.id.nav_host_fragment);

Але контролер може знадобиться і у фрагменті, який знаходиться в контейнері. У прикладах це були Fragment1, Fragment2 тощо.

У цих фрагментах, контролер може бути отриманий так:

navController = Navigation.findNavController(view);

Де view - це будь-який View у цьому фрагменті.

Приклад використання в OnClickListener

buttonNext.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       Navigation.findNavController(view).navigate(R.id.fragment2);
   }
});

Персонально для OnClickListener, до речі, створено окремий метод Navigation.createNavigateOnClickListener, що дає змогу повісити обробник на кнопку так:

button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.fragment2));

Після натискання на кнопку буде виконано навігацію до fragment2.

NavigationUI - набір методів, що дають змогу інтегрувати Navigation Component із меню, Navigation Drawer і BottomNavigationView.

Для використання, необхідно додати в dependencies:

implementation 'android.arch.navigation:navigation-ui:2.8.5'

Overflow menu

Є таке меню

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
 
   <item android:id="@+id/fragment1"
       android:title="Fragment 1"/>
 
   <item android:id="@+id/fragment2"
       android:title="Fragment 2"/>
 
   <item android:id="@+id/fragment3"
       android:title="Fragment 3"/>
 
</menu>

Воно відображатиметься в Activity.

Зверніть увагу на ID, які я використовував у меню: @+id/fragment1, і т.д. Ті самі ID використані для destination у графі:

<?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"
   app:startDestination="@id/fragment1">
   <fragment
       android:id="@+id/fragment1"
       android:name="ru.startandroid.navigation.Fragment1"
       android:label="@string/fragment_1_title"
       tools:layout="@layout/fragment1" />
   <fragment
       android:id="@+id/fragment2"
       android:name="ru.startandroid.navigation.Fragment2"
       android:label="@string/fragment_2_title"
       tools:layout="@layout/fragment2" />
   <fragment
       android:id="@+id/fragment3"
       android:name="ru.startandroid.navigation.Fragment3"
       android:label="@string/fragment_3_title"
       tools:layout="@layout/fragment3" />
</navigation>

Тепер в обробці натискань використовуємо метод NavigationUI.onNavDestinationSelected.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   NavigationUI.onNavDestinationSelected(item, navController);
   return super.onOptionsItemSelected(item);
}

Під капотом буде виконана навігація до destination з ID = item.getItemId().

Відповідно під час натискання на пункт меню з ID = fragment2, буде виконано навігацію до destination з тим самим ID, тобто fragment2.

Результат:

Для пунктів меню можна використовувати ID не тільки від destination, а й від action.

Є Drawer, який відображає таке меню

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
 
   <group android:checkableBehavior="single">
 
   <item android:id="@+id/fragment1"
       android:title="Fragment 1"/>
 
   <item android:id="@+id/fragment2"
       android:title="Fragment 2"/>
 
   <item android:id="@+id/fragment3"
       android:title="Fragment 3"/>
 
   </group>
 
</menu>

Зазвичай, щоб обробляти натискання на ці пункти меню, ми вішаємо обробник на NavigationView, який всередині DrawerLayout.

Але замість цього ми можемо зробити так:

NavigationUI.setupWithNavController(navigationView, navController);

Цей метод сам повісить обробник на NavigationView і після натискання на пункти меню виконуватиме навігацію до destination (або action) з тим самим ID, що й у натиснутого пункту меню. Також він сам виділятиме пункт меню (setChecked) і закриватиме Drawer.

При цьому параметру Pop To буде задано стартовий destination. Тобто системна кнопка Back завжди повертатиме нас у Fragment 1.

Результат

Усе ок, але можна зробити ще краще.

NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);

Додаємо інтеграцію ActionBar. Тепер під час навігації в ActionBar буде поміщатися Label у destination. І іконка змінюватиметься, якщо перебуваємо не в стартовому destination.

Натискання на Home обробляємо самі

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
       case android.R.id.home:
           drawerLayout.openDrawer(GravityCompat.START);
           return true;
   }
   return super.onOptionsItemSelected(item);
}

BottomNavigationView

Є BottomNavigationView, що відображає меню:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
 
   <item
       android:id="@+id/fragment1"
       android:title="Fragment 1" />
 
   <item
       android:id="@+id/fragment2"
       android:title="Fragment 2" />
 
   <item
       android:id="@+id/fragment3"
       android:title="Fragment 3" />
 
</menu>

Щоб обробити його натискання, ми зазвичай вішаємо обробник. За нас це може зробити Navigation.

NavigationUI.setupWithNavController(bottomNavigationView, navController);

Метод setupWithNavController вішає лістенер на BottomNavigationView і виконує навігацію при натисканні на його елементи. При цьому виконує setChecked для натиснутого елемента.

Результат: Системна кнопка Back завжди повертатиме нас на стартовий destination.

Можна додати інтеграцію з ActionBar:

NavigationUI.setupActionBarWithNavController(this, navController);

Тепер під час навігації в ActionBar буде поміщатися Label у destination. І іконка змінюватиметься, якщо перебуваємо не в стартовому destination.

У цьому випадку треба самим обробити натискання на Home.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
       case android.R.id.home:
           navController.popBackStack();
           return true;
   }
 
   return super.onOptionsItemSelected(item);
}

Результат: