Шаблон Navigation Drawer Activity
Метки: NavigationView , DrawerLayout , Material Design
Рассмотрим шаблон Navigation Drawer Activity. Создадим новый проект и выберем нужный шаблон.
Для беглого знакомства можете сразу запустить проект и посмотреть его в действии. При запуске приложение выглядит как обычная программа «Hello World». Но есть и отличия. Нажмите на значок в виде трёх горизонтальных полосок в заголовке. Значок в документации называется «гамбургером» (Hamburger menu). Это официальная позиция Гугла. Но в реальности значок символизирует полосатых котов (никому не рассказывайте). При нажатии слева вылезет навигационная шторка. Шторка работает как обычная шторка в ванной. По высоте она занимает весь экран, включая системную область. Можете подвигать шторку вперёд-назад, чтобы увидеть, что верхняя кромка шторки в системной области полупрозрачна и не закрывает системные значки. Подобное поведение доступно на устройствах под Android 5 и выше. На старых устройствах шторка находится под системной панелью. Недавно стал проверять работу под Android 8.0 и увидел, что шторка теперь не закрывает системную панель. Ниже для сравнения я привёл два варианта.
Шторка закрывает системную панель
Шторка не закрывает системную панель
Сама шторка состоит из двух основных частей — в верхней части находится картинка и текст, а в нижней — меню со значками. Меню в свою очередь разделено на две группы. В верхней части значки можно выбрать и выбранный пункт останется выделенным. В нижней части меню пункты не выделяются. Уберите шторку обратно и вызовите теперь её не нажатием на значок гамбургера, а движением пальца от края экран в центр. Получилось? Отлично, а теперь выдвигайте шторку медленно и наблюдайте за значком гамбургера. Вы увидите, что во время движения значок трансформируется. К сожалению, шторка закрывает значок и непонятно, во что превращаются три полоски. А превращаются они в три кота ж! стрелку. Позже я покажу, как увидеть её. А может не покажу, я ещё не решил.
Возвращаемся в студию и начинаем изучать код проекта.
Если открыть файл activity_main.xml в режиме Design, то можно увидеть, как будет выглядеть приложение с открытой шторкой.
Небольшие расхождения имеются, но в целом совпадает с реальным приложением.
Посмотрим на его содержание.
Сейчас важно запомнить, что за выдвигающую шторку отвечает элемент NavigationView, который входит последним в контейнере DrawerLayout и представляет собой навигационное меню. А перед меню находится вставка include, указывающая на разметку app_bar_main.xml.
Атрибут tools:openDrawer позволяет указать студии, что навигационное меню нужно отобразить в раскрытом виде в режиме просмотра разметки.
NavigationView
В 2014 году Google показал новый дизайн и различные новые примеры по навигации. Но вначале они использовали подручные средства, которые были под рукой — фрагменты.
Спустя год компания разработала на основе предка FrameLayout новый компонент NavigationView, который стал частью библиотеки Android Design Support Library.
Новый подход оказался неожиданным, но логичным. Раз выдвижная шторка содержит навигационное меню, то и класс был спроектирован как меню. Вы можете создать элементы меню в ресурсах res/menu стандартным способом и получить готовую навигацию.
Необходимые рекомендации по созданию навигационной выдвижной шторки можно найти на странице Navigation drawer — Patterns.
Перейдём к деталям.
Теперь в рекомендациях не указаны точные размеры шторки, хотя раньше нужно было самостоятельно указать ширину шторки. Видимо, новый класс сам обеспечивает необходимую ширину. Интерес представляют два последних атрибута.
Тег NavigationView содержит ссылку на собственную разметку в атрибуте app:headerLayout, который указывает на файл nav_header_main.xml (верхняя часть шторки), а также на меню в атрибуте app:menu, который ссылается на ресурс меню menu/activity_main_drawer.xml.
Откроем файл nav_header_main.xml и посмотрим на разметку шторки.
Разметка состоит из ImageView и двух TextView, размещённых в контейнере LinearLayout. Фон контейнера определён в ресурсе drawable/side_nav_bar.xml и представляет собой градиент.
Остальные атрибуты понятны и не требуют пояснений.
Можно (но не нужно) настроить верхнюю часть шторки не через XML, а программно.
После обновления одной из версий библиотеки Design Support, доступ к шапке осуществляется теперь через другой код.
Теперь рассмотрим ресурс навигационного меню res/menu/activity_main_drawer.xml.
Принцип создания элементов меню остался стандартным. Каждый пункт меню представляет собой тег item с указанием значка и текста. Для группировки используется элемент group. Поведение элементов меню в группе регулируется атрибутом android:checkableBehavior. В примере используется значение single — при нажатии на пункт меню, он останется выделенным (принцип переключателя RadioButton). Всего доступно три варианта.
- single – можно выбрать один элемент группы (переключатель)
- all — можно выбрать все элементы группы (флажок)
- none – элементы не выбираются
В библиотеке Android Support Design версии 23 вариант all не работает и будет действовать, как со значением single.
Также следует обратить внимание, что теперь проект ссылается на векторные рисунки, которые находятся в папке drawable-21.
Осталось рассмотреть тег include, который ссылается на файл ресурса res/layout/app_bar_main.xml. Он вам будет знаком по шаблону Blank Activity, который мы изучали в статье Библиотека Android Support Design. Только там он находился в файле activity_main.xml, а здесь его перенесли в файл app_bar_main.xml. Всё остальное осталось без изменений. Повторяться не будем.
Теперь изучим код активности для работы со шторкой.
В классе активности реализуется интерфейс OnNavigationItemSelectedListener с его методом onNavigationItemSelected():
Логика кода ничем не отличается о работы с обычным меню. Определяется идентификатор выбранного пункта и далее вам нужно написать свой код. Затем вызывается метод closeDrawer() для закрытия шторки.
Добавим код для первого пункта меню.
При нажатии кнопки «Назад» проверяется состояние шторки. Если шторка открыта (isDrawerOpen()), то её закрываем с помощью метода closeDrawer().
В методе onCreate() происходит инициализация шторки.
Теперь поговорим об изменениях, которые можно внести в проект.
Хотите выдвигать шторку справа? Установите значение end у атрибута layout_gravity. Обычно используется для стран с обратным порядком букв.
На самом деле смысла в этом не оказалось. Да, шторка выдвигается вручную. Но если нажать на значок гамбургера, то приложение валится с ошибкой. Любое нажатие в меню шторки также приводит к ошибке. Теоретически можно написать код, который исправит проблему, но он будет сложным. Забудьте об этом совете.
Тем не менее, можно реализовать забавный эффект — добавить вторую шторку на экран. Первая будет работать главной и реагировать на нажатие значка, а вторая будет дополнительной для вывода какой-то информации. Достаточно в разметку добавить второй NavigationView с атрибутом android:layout_gravity=»end»
Кстати, если выдвигать правую шторку, то можно наблюдать трансформацию значка без помех. Скриншот во время частичного выдвижения шторки справа.
Для изменения цвета значков и текста в навигационном меню используйте атрибуты app:itemIconTint и app:itemTextColor.
Данным атрибутам соответствуют методы setItemIconTintList() и setItemTextColor() компонента NavigationView.
Так как наличие шторки не совсем очевидно, можно при первом запуске показать шторку в раскрытом состоянии. Далее можно запомнить состояние шторки при первом запуске в настройках, чтобы во второй раз выводить её уже в закрытом состоянии. Можете самостоятельно реализовать эту возможность.
Напоследок покажу превращение значка гамбургера в стрелку в явном виде, как и обещал в начале статьи. Напомню, что по рекомендации Material Design шторка должна закрывать всю область экрана. Но если вы хотите поместить шторку под заголовком, то следует немного поправить разметку. Откроем файл app_bar_main.xml и вырежем из него небольшой кусок. Затем в файле activity_main.xml добавим LinearLayout в качестве корневого контейнера и вставим скопированный ранее кусок кода.
Смотрим на значок.
Сама анимация значка зависит от переменной toggle (объект класса ActionBarDrawerToggle). Если вы её уберёте, то никакого значка в заголовке приложения не будет.
Можно поменять цвет значка гамбургера. Откроем файл стилей res/values/styles.xml и добавим:
Элемент spinBars со значением true позволяет использовать анимацию. В противном случае значок будет статичным.
Навигация
В шаблоне присутствует метод onNavigationItemSelected() с аннотацией @SuppressWarnings(«StatementWithEmptyBody») (Оператор с пустым телом). Нам нужно добавить свой код для навигации, который должен реагировать на нажатия в меню шторки. Нам понадобятся фрагменты. Для примера создадим первый фрагмент.
Разметка для первой активности.
По такому же образу создайте второй фрагмент SecondFragment и т.д.
Определим RelativeLayout в файле content_main.xml в качестве контейнера.
Теперь можем написать недостающий код для навигации по фрагментам в MainActivity.
Добавляем счётчик в меню шторки
Откройте файл res/menu/activity_main_drawer.xml и добавьте атрибут app:actionViewClass=»android.widget.TextView» ко второму и третьему элементу меню из шаблона. Теперь эти элементы будут связаны с текстовыми метками.
Объявим текстовые метки и инициализируем их в методе onCreate(). В отдельном методе будем управлять их свойствами.
Вы можете переделать метод под себя, чтобы динамически изменять показания счётчика.
Сдвигаем содержимое экрана
При выдвижении шторки можно сдвинуть основное содержание. Потребуется небольшая модификация кода. Для начала нужно присвоить идентификатор контейнеру ConstraintLayout в content_main.xml.
В MainActivity добавим экземпляру ActionBarDrawerToggle метод onDrawerSlide() и сдвинем содержимое на определённую величину. При желании можно также изменить размер, используя второй параметр метода slideOffset.
Дополнительное чтение
Библиотека mxn21/FlowingDrawer с прикольным эффектом.
Источник
Урок 24. Navigation Architecture Component. Введение
В этом уроке рассмотрим простой пример использования 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 файле модуля:
Рассмотрим простой пример с 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:
Контейнер готов. Остается где-то взять контроллер (NavController). Тут нам поможет контейнер. Он при создании сам создаст контроллер и чуть позже поделится им с нами.
Обратите внимание, что в атрибуте navGraph мы указали созданный ранее граф main_graph. Контейнер передаст этот граф контроллеру.
Переходим к коду.
Чтобы попросить контроллер у контейнера, используем метод Navigation.findNavController с указанием ID контейнера. Этот метод по ID найдет контейнер NavHostFragment и возьмет у него контроллер.
Код в MainActivity.java
Теперь мы можем использовать этот контроллер для навигации по фрагментам. Для этого есть два метода:
navigate(int resId) — чтобы открыть какой-либо фрагмент из графа, надо у контроллера вызвать метод navigate и передать ему ID destination. Контроллер просмотрит граф, определит какому фрагменту в графе соответствует ID и отобразит этот фрагмент.
popBackStack — возврат на шаг назад, на предыдущий фрагмент.
Как вы уже видели, в каждом из трех фрагментов есть кнопки Back и Next. По нажатию на кнопку Next мы будем открывать следующий фрагмент. А по нажатию на кнопку Back будем возвращаться на предыдущий. Я использовал колбэки и обработку нажатий на эти кнопки вытащил в Activity. Соответственно в MainActivity у меня 6 методов (3 фрагмента, 2 кнопки в каждом)
В этих методах мы и будем работать с контроллером.
По названию метода понятно для какой кнопки какого фрагмента он является обработчиком.
Например, по нажатию на Next в Fragment1 мы просим контроллер открыть destination с Контроллер найдет этот destination в графе, увидит, что это фрагмент Fragment2 и в контейнере отобразит этот фрагмент.
Аналогично по нажатию на Next в Fragment2 мы просим открыть destination с который в графе соответствует фрагменту Fragment3.
По нажатию на кнопки Back, мы возвращаемся на шаг назад.
Запускаем приложение. При старте сразу отобразится Fragment1, потому что он является стартовым в графе.
Системная кнопка Back тоже работает и выполняет шаг назад. Это происходит благодаря атрибуту
который мы указали в контейнере (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.
Контроллер сделает следующее:
1) возьмет текущий destination (который сейчас отображается в контейнере, т.е. destination fragment1)
2) найдет у него action с >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 создан, его >
По нажатию на кнопку Next в Fragment3 будем вызывать этот destination
Контроллер найдет в графе, что destination с таким ID соответствует SecondActivity и запустит его.
На этом полномочия графа main_graph заканчиваются. В новом Activity нам нужен новый граф.
Создаем second_graph и добавляем туда Fragment4 и Fragment5
Fragment4 — стартовый, он будет отображен при открытии SecondActivity.
В layout activity_second добавляем контейнер NavHostFragment и указываем граф second_graph
В SecondActivity находим контроллер и в обработчиках нажатий кнопок фрагментов используем его для навигации.
Из фрагмента 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 с И мы открываем его вызовом метода navController.navigate(R.id.configScreen).
Внезапно мы решаем, что надо экран конфигурации вынести в отдельное ConfigActivity. Создаем это Activity, переносим все туда и добавляем его в граф вместо ConfigFragment, под тем же >
При этом в приложении вообще никак не меняется код вызова экрана конфигурации. Это так и остается вызов метода navigate с указанием Но теперь контроллер будет открывать не ConfigFragment в текущем контейнере, а запустит новое Activity, потому что мы настроили это в графе.
А вот так выглядит содержимое графа main_graph.xml:
Три фрагмента, одно Activity и у первого фрагмента есть action, который ведет во второй фрагмент.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник