В этом уроке рассмотрим простой пример использования Navigation Architecture Component.

 


Полный список уроков курса:


 

 

 

На Google IO был представлен Navigation Architecture Component. На сегодняшний день (28.05.2018) он в альфа версии, поэтому использовать его в рабочих проектах пока рановато, но ознакомиться уже можно. Ближайшие уроков 5 будут о нем.

Я подробно просмотрел исходники этого компонента, чтобы точно понимать, что он делает. Под капотом там все те же startActivity и FragmentManager. Navigation Architecture Component - это обертка над этими стандартными механизмами, которая призвана упорядочить и упростить навигацию в приложении. 

 

Для полноценной работы с Navigation нам понадобится Android Studio 3.2. Пока что есть только Canary сборка, она вполне подойдет.

Navigation функционал может быть выключен по умолчанию. Проверьте настройки студии Settings > Experimental > Enable Navigation Editor. Перезапустите студию после включения этого чекбокса.

 

dependencies в build.gradle файле модуля:

implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha02'

 

Рассмотрим простой пример с MainActivity и тремя фрагментами: Fragment1, Fragment2, Fragment3. MainActivity будет поочередно отображать в себе фрагменты.

 

Для этого нам понадобятся следующие Navigation инструменты:

NavController - этот основной механизм Navigation Component. Именно его мы будем просить показывать на экране фрагменты. Но чтобы он мог это делать, он должен иметь список фрагментов и контейнер, в котором он будет эти фрагменты отображать.

NavGraph - это список фрагментов, который мы будем создавать и наполнять. NavController сможет показывать фрагменты только из этого списка. Далее будем называть его графом.

NavHostFragment - это контейнер. Внутри него NavController будет отображать фрагменты.

Еще раз, кратко, для понимания: контроллер в контейнере отображает фрагменты из графа.

 

 

Начнем с создания графа (NavGraph). Это обычный resource файл с типом Navigation.

 

После создания он пуст:


Давайте добавлять фрагменты. В терминологии Navigation они называются destination.

Жмем кнопку добавления, студия показывает нам фрагменты и Activity, которые есть в проекте. Добавляем три фрагмента.

 

Результат

Слева видим список всех destination в этом графе. Пометкой Start отмечен стартовый destination, который сразу будет отображен при запуске приложения. В нашем случае это Fragment1.

Посередине отображены те же destination, но уже не списком, а в их реальном виде, с использованием их layout. Значком домика помечен стартовый destination. Для всех трех фрагментов я создал одинаковые layout: название фрагмента и пара кнопок. Позже будем использовать эти кнопки для навигации.

Справа находится панель атрибутов для текущего выделенного destination. О них мы подробно поговорим позже. Пока что нас интересует атрибут ID. Этот ID нам надо будет сообщать контроллеру (NavController), чтобы он отобразил соответствующий фрагмент. Этот ID кстати отображается в панели посередине. Над каждым фрагментом можно увидеть его ID.

 

Ок, граф создан. Теперь в MainActivity надо добавить контейнер (NavHostFragment), в котором NavController будет отображать фрагменты.

В activity_main добавляем NavHostFragment:

<FrameLayout 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:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <fragment
       android:id="@+id/nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:defaultNavHost="true"
       app:navGraph="@navigation/main_graph" />

</FrameLayout>

Контейнер готов. Остается где-то взять контроллер (NavController). Тут нам поможет контейнер. Он при создании сам создаст контроллер и чуть позже поделится им с нами.

Обратите внимание, что в атрибуте navGraph мы указали созданный ранее граф main_graph. Контейнер передаст этот граф контроллеру.

 

Переходим к коду.

Чтобы попросить контроллер у контейнера, используем метод Navigation.findNavController с указанием ID контейнера. Этот метод по ID найдет контейнер NavHostFragment и возьмет у него контроллер.

 

Код в MainActivity.java

NavController navController;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   navController = Navigation.findNavController(this, R.id.nav_host_fragment);
}

Теперь мы можем использовать этот контроллер для навигации по фрагментам. Для этого есть два метода:

navigate(int resId) - чтобы открыть какой-либо фрагмент из графа, надо у контроллера вызвать метод navigate и передать ему ID destination. Контроллер просмотрит граф, определит какому фрагменту в графе соответствует ID и отобразит этот фрагмент.

popBackStack - возврат на шаг назад, на предыдущий фрагмент.

 

Как вы уже видели, в каждом из трех фрагментов есть кнопки Back и Next. По нажатию на кнопку Next мы будем открывать следующий фрагмент. А по нажатию на кнопку Back будем возвращаться на предыдущий. Я использовал колбэки и обработку нажатий на эти кнопки вытащил в Activity. Соответственно в MainActivity у меня 6 методов (3 фрагмента, 2 кнопки в каждом)

В этих методах мы и будем работать с контроллером.

   @Override
   public void onFragment1NextClick() {
       navController.navigate(R.id.fragment2);
   }

   @Override
   public void onFragment1BackClick() {}


   @Override
   public void onFragment2NextClick() {
       navController.navigate(R.id.fragment3);
   }

   @Override
   public void onFragment2BackClick() {
       navController.popBackStack();
   }


   @Override
   public void onFragment3NextClick() {}

   @Override
   public void onFragment3BackClick() {
       navController.popBackStack();
   }

По названию метода понятно для какой кнопки какого фрагмента он является обработчиком.

Например, по нажатию на Next в Fragment1 мы просим контроллер открыть destination с ID = fragment2. Контроллер найдет этот destination в графе, увидит, что это фрагмент Fragment2 и в контейнере отобразит этот фрагмент.

Аналогично по нажатию на Next в Fragment2 мы просим открыть destination с ID = fragment3, который в графе соответствует фрагменту Fragment3.

По нажатию на кнопки Back, мы возвращаемся на шаг назад.

 

Запускаем приложение. При старте сразу отобразится Fragment1, потому что он является стартовым в графе.

Выполняем навигацию.

Системная кнопка Back тоже работает и выполняет шаг назад. Это происходит благодаря атрибуту

app:defaultNavHost="true"

который мы указали в контейнере (NavHostFragment). Контейнер перехватывает нажатия и показывает предыдущий фрагмент.

 

Если установить его значение в false, то контейнер больше не будет обрабатывать системную кнопку Back, и Activity будет закрываться.

 

 

 

Action

При вызове destination нам может понадобиться задать некоторые параметры, например, анимацию, аргументы и т.п. Для этого используется action.

Давайте создадим action, который будет выполнять переход от fragment1 к fragment2

Для destination fragment1 мы создали action, который ведет в destination fragment2.

 

У action есть различные параметры, которые мы можем настраивать в редакторе графа. Они будут использованы при переходе от destination fragment1 к destination fragment2.

Мы разберем их подробно в следующих уроках. Пока нас снова интересует только значение атрибута ID. Мы можем использовать его при вызове метода navigate, чтобы вызвать action. Давайте сделаем это по нажатию на кнопку Next в Fragment1.

@Override
public void onFragment1NextClick() {
   navController.navigate(R.id.action_fragment1_to_fragment2);
}

Контроллер сделает следующее:
1) возьмет текущий destination (который сейчас отображается в контейнере, т.е. destination fragment1)
2) найдет у него action с ID = action_fragment1_to_fragment2
3) определит, что этот action ведет в destination fragment2
4) определит, что destination fragment2 - это фрагмент Fragment2
5) отобразит Fragment2 и при этом применит параметры, которые были заданы в action_fragment1_to_fragment2

Если мы попытаемся вызвать action не находясь в destination, которому этот action принадлежит, то будет ошибка. Т.е. action action_fragment1_to_fragment2 мы можем вызывать только находясь в destination fragment1, потому что при создании action мы рисовали его из destination fragment1.

 

Из одного destination можно создать несколько action:

 

 

 

Activity 

В качестве destination мы можем использовать не только фрагменты, но и Activity.

В этом же примере я создал SecondActivity и фрагменты Fragment4 и Fragment5. Будем вызывать их из Fragment3, находящемся в MainActivity.

 

Открываем граф main_graph и добавляем SecondActivity, как новый destination.

destination создан, его ID = secondActivity

По нажатию на кнопку Next в Fragment3 будем вызывать этот destination

@Override
public void onFragment3NextClick() {
   navController.navigate(R.id.secondActivity);
}

Контроллер найдет в графе, что destination с таким ID соответствует SecondActivity и запустит его.

На этом полномочия графа main_graph заканчиваются. В новом Activity нам нужен новый граф.

 

Создаем second_graph и добавляем туда Fragment4 и Fragment5

Fragment4 - стартовый, он будет отображен при открытии SecondActivity.

 

В layout activity_second добавляем контейнер NavHostFragment и указываем граф second_graph

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".SecondActivity">

   <fragment
       android:id="@+id/nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:defaultNavHost="true"
       app:navGraph="@navigation/second_graph" />

</FrameLayout>

 

В SecondActivity находим контроллер и в обработчиках нажатий кнопок фрагментов используем его для навигации.

   NavController navController;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_second);
       navController = Navigation.findNavController(this, R.id.nav_host_fragment);
   }

   @Override
   public void onFragment4NextClick() {
       navController.navigate(R.id.fragment5);
   }

   @Override
   public void onFragment4BackClick() {}

   @Override
   public void onFragment5NextClick() {}

   @Override
   public void onFragment5BackClick() {
       navController.popBackStack();
   }

 

Запускаем приложение

Из фрагмента Fragment3 переходим в SecondActivity. Там сразу открывается Fragment4, т.к. он стартовый. Из него переходим в Fragment5 и обратно. А вот возвращаться из SecondActivity в MainActivity приходится с помощью системной кнопки Back. Контроллер в SecondActivity работает только в пределах этого Activity. Он ничего не знает за его пределами. Он не знает, что делать, когда вызывается popBackStack в стартовом фрагменте, т.е. в Fragment4. Тут уже нам надо самим. Например, можно в onFragment4BackClick вызывать метод finish, чтобы закрыть Activity.

 

Метод navController.popBackStack возвращает boolean. Если контроллер сам смог вернуться на шаг назад, то он вернет true. Если же он не знает, что делать, то вернет false и в этом случае мы сами можем обработать эту ситуацию.

 

 

 

destination 

Напоследок пару слов о понятии destination в Navigation Component. В грАфе у каждого destination есть ID, и мы указываем этот ID в методе navigate, когда просим контроллер открыть destination. При этом нам не важно, чем является в графе этот destination - Activity или фрагментом. Это забота контролера. Он сам это определит и вызовет либо startActivity, либо будет работать с FragmentManager.

Например, у нас в приложении есть экран конфигурации. Это фрагмент ConfigFragment. В графе у нас этот фрагмент фигурирует как destination с ID = configScreen. И мы открываем его вызовом метода navController.navigate(R.id.configScreen).

Внезапно мы решаем, что надо экран конфигурации вынести в отдельное ConfigActivity. Создаем это Activity, переносим все туда и добавляем его в граф вместо ConfigFragment, под тем же ID = configScreen.

При этом в приложении вообще никак не меняется код вызова экрана конфигурации. Это так и остается вызов метода navigate с указанием ID = configScreen. Но теперь контроллер будет открывать не ConfigFragment в текущем контейнере, а запустит новое Activity, потому что мы настроили это в графе.

 

 

А вот так выглядит содержимое графа main_graph.xml:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   app:startDestination="@id/fragment1">
   <fragment
       android:id="@+id/fragment1"
       android:name="ru.startandroid.navigation.Fragment1"
       android:label="fragment1"
       tools:layout="@layout/fragment1" >
       <action
           android:id="@+id/action_fragment1_to_fragment2"
           app:destination="@id/fragment2" />
   </fragment>
   <fragment
       android:id="@+id/fragment2"
       android:name="ru.startandroid.navigation.Fragment2"
       android:label="fragment2"
       tools:layout="@layout/fragment2" />
   <fragment
       android:id="@+id/fragment3"
       android:name="ru.startandroid.navigation.Fragment3"
       android:label="fragment3"
       tools:layout="@layout/fragment3" />
   <activity
       android:id="@+id/secondActivity"
       android:name="ru.startandroid.navigation.SecondActivity"
       android:label="activity_second"
       tools:layout="@layout/activity_second" />
</navigation>

Три фрагмента, одно Activity и у первого фрагмента есть action, который ведет во второй фрагмент. 

 

 

 


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

- новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме 




Language

Социальные сети

 

Telegram канал



Android чат в Telegram



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal