- Урок 17. Android Navigation. Знакомство с BottomNavigationView. Как добавить фрагменты в панель Bottom Navigation.
- Создаем проект
- Неоходимые библиотеки
- Граф навигации
- Добавление экранов
- Хост навигации
- BottomNavigationView
- Добавление пунктов меню
- Добаление идентификаторов в контроллер навигации
- Рефакторинг
- Как добавить BottomNavigationView в ваше android приложение
- Navigation Component-дзюцу, vol. 1 — BottomNavigationView
- Disclaimer
- Кейсы с BottomNavigationView
- Первый опыт
- Пора запускать приложение
- У нас есть workaround
- Адаптация workaround-а для фрагментов
- Выводы по работе с BottomNavigationView
Урок 17. Android Navigation. Знакомство с BottomNavigationView. Как добавить фрагменты в панель Bottom Navigation.
На прошлом уроке мы работали с анимацией переходов между экранами. На этом уроке познакомимся с нижней панелью навигации BottomNavigationView, которая позволяет переходить между экранами – пунктами назначения навигации, а также наглядно информирует пользователя о том, на каком экране он находится. Разберемся, как добавить Bottom Navigation в андроид приложение и как добавить в BottomNavigationView новые фрагменты.
Создаем проект
С одной стороны, создать простое приложение с панелью навигации можно, не написав ни одной строчки кода. Достаточно воспользоваться готовым макетом Bottom Navigation Activity на этапе создания проекта в Android Studio. При этом создается проект приложения с нижней панелью навигации BottomNavigationView на главном экране, в которой отображается три пункта. При нажатии каждого из них меняются экраны приложения. Это все хорошо, скажете вы, но если нужно добавить или убрать экраны и пункты для них? Или добавить нижнюю навигацию в существующее приложение?
Ок, давайте изучим структуру проекта, созданного по шаблону, и по ходу попробуем добавить сюда пару новых экранов и пунктов для них, а также рассмотрим шаги для добавления нижней панели навигации в уже существующее приложение.
Неоходимые библиотеки
Если открыть файл сборки build.gradle, в секции dependencies можно увидеть подключенные библиотеки из пакета androidx.navigation (с ktx для проектов на kotlin) которые нам уже знакомы по предыдущим урокам на тему навигации в приложении. Если вы добавляете нижнюю навигацию в существующее приложение, то начинать нужно с добавления этих библиотек в ваш проект.
Граф навигации
В папке res есть папка navigation, в которой размещен все тот же граф навигации, его вы помните по предыдущим урокам. Без него, как вы понимаете, не обходится здесь и нижняя панель навигации.
Добавление экранов
Нажмите кнопку New Destination вверху и выберите Create New Destination. На экране добавления фрагментов выберите шаблон Fragment (with ViewModel). Добавьте один за другим два фрагмента – четвертый и пятый экран. Я их так и назову: FourthFragment и FifthFragment.
Вы можете выбрать при добавлении просто Fragment blank. Но связка Fragment + ViewModel предоставляет преимущества сохранения состояния экрана при изменении конфигурации – например, при повороте устройства. Подробнее об этом мы говорили в Уроке 6.
В граф навигации добавилось два фрагмента. Переименуйте их идентификаторы в соответствии с именами других пунктов назначения: navigation_fourth и navigation_fifth. Переименование всегда лучше делать через рефакторинг, нажатием комбинации Shift + F6 – это гарантирует изменение всех возможных связей.
Можно было оставить идентификаторы как было, но более правильно с точки зрения Code Convention, чтобы названия однотипных элементов были логически похожими. Да и находить их будет легче потом.
Также измените значение параметра android:label — оно используется для заголовка экрана. Впишите сюда текст, который вы хотите видеть в заголовке соответствующего экрана, желательно сохранить его затем в строковых ресурсах. Для сохранения нажмите желтую лампочку и выберите пункт «Extract string resourse», а затем придумайте название строки, которая сохранится в файле res/values/strings.xml.
На этом добавление экранов закончено. Но, если запустить приложение сейчас, они не будут отображаться, и нижняя панель навигации никак не изменится. Что ж, идем дальше.
Хост навигации
Если мы откроем папку layout, то увидим макеты всех фрагментов – пунктов назначения, и макет главного активити.
Макет activity_main.xml содержит компонент fragment – это хост навигации, в котором отображаются фрагменты – пункты назначения. Он связан с графом навигации через параметр app:navGraph.
BottomNavigationView
Также здесь присутствует и компонент BottomNavigationView. Это и есть нижняя панель навигации. Компонент BottomNavigationView является частью библиотеки компонентов материального дизайна. Эта библиотека должна быть импортирована в файле сборки модуля:
Обычно она уже есть в проекте по умолчанию.
Рассмотрим компонент BottomNavigationView боле подробно. Он содержит идентификатор nav_view, и имеет значение ширины – 0dp. Это рекомендуемое значение для всех элементов, ширина которых регулируется ограничениями, заданными корневым элементом разметки ConstraintLayout. Мы видим эти ограничения ниже, они привязывают панель к нижней части родителя, который, в свою очередь, занимает весь экран.
Высота BottomNavigationView задана по контенту, но можно установить фиксированную нужного вам размера.
Далее идут два параметра layout_marginStart и layout_marginEnd. Они регламентируют отступ слева и справа, но поскольку значения здесь равны нулю, они ни на что не влияют, и их можно удалить.
Параметр android:background содержит ссылку на атрибут windowBackground и делает фон панели такой же, как фон экрана. Если вы хотите получить цвет панели, такой как на гайдлайнах сайта material.io, то замените параметр android:background=»?android:attr/windowBackground» таким:
Кстати, на сайте material.io можно найти много полезных рекомендаций как по дизайну, так и по реализации компонентов визуального интерфейса. На странице Bottom Navigation, например, указано, что рекомендованное число элементов панели нижней навигации должно быть от трех до пяти. Подписи должны быть максимально короткими и состоять, по возможности, из одного слова.
На вкладе implementation дается пример реализации нижней панели без графа навигации, а также примеры кастомизации и оформления фона и цвета пунктов нижней панели, добавления пунктам бейджей и т.д.
Добавление пунктов меню
Но вернемся к нашему проекту и компоненту BottomNavigationView в макете activity_main.xml.
Нас интересует последний параметр:
Он ссылается на файл bottom_nav_menu.xml в папке res/menu. Этот файл содержит описание пунктов меню нижней панели навигации. Следовательно, добавлять новые пункты следует здесь.
Добавим пару элементов для четвертого и пятого фрагментов. Их идентификаторы должны совпадать с идентификаторами пунктов назначения в графе навигации. Также укажите соответствующие строковые ресурсы в качестве названия пунктов в android:title.
Добаление идентификаторов в контроллер навигации
Осталось добавить идентификаторы пунктов навигации в конфигурацию контроллера навигации в классе MainActivity.
Переменная класса АppBarConfiguration содержит набор идентификаторов пунктов навигации и передается вместе с контроллером – хостом навигации – в функцию setupActionBarWithNavController. Это нужно для того, чтобы система считала каждый из пунктов назначения пунктом верхнего уровня. В противном случае каждый фрагмент будет отображать стрелку возврата назад в тулбаре слева, как это отображается на дочерних активити и фрагментах.
Попробуйте убрать какой-либо из идентификаторов пунктов назначения из набора, переданного в переменную appBarConfiguration, запустите приложение и посмотрите на экран пропущенного пункта назначения, и вы поймете, о чем речь.
Теперь, когда добавление закончено, запустите приложение. Все добавленные пункты меню должны отображаться в нижней панели навигации, и при нажатии вести на соответствующие экраны.
Рефакторинг
Теперь можно навести порядок в проекте. Дело в том, что изначально в проекте фрагменты каждого экрана со своими ViewModel расположены в отдельных папках по имени каждого пункта меню нижней панели навигации, и все это лежит в папке ui. Новые же добавленные фрагменты попали просто в главный пакет. Нужно создать в папке ui новые пакеты по имени добавленных экранов. Затем нужно перенести туда добавленные фрагменты вместе с их привязанными ViewModel. Делается это простым перетаскиванием в дереве проекта, с открытием окна рефакторинга, в котором нужно подтвердить операцию.
Теперь вы знаете, как добавить Bottom Navigation в приложение.
На этом наш урок закончен. Вопросы задавайте в комментариях. Исходный код проекта можно скачать по ссылке.
Источник
Как добавить BottomNavigationView в ваше android приложение
Рассмотрим новый компонент библиотеки поддержки материального дизайна BottomNavigationView. Это нижняя панель навигации, позволяющая переключаться между экранами приложения в одно касание, она предназначена в основном для смартфонов, поскольку расположение в нижней части экрана обеспечивает удобный и быстрый доступ для пользователя.
Этот компонент доступен в Design Support Library с версии 25.0.0 и Google рекомендует его к применению с соблюдением таких принципов дизайна:
- Рекомендуется только для смартфонов, на планшетах и других устройствах с большим экраном лучше использовать боковое меню навигации.
- Ширина областей значков зависит от их количества и может варьироваться от 80 до 168 dp. Высота панели — 56 dp, размер иконки 24×24 dp.
- Панель должна содержать не меньше 3-х, но не больше 5-ти кнопок, кнопки должны помещаться на экране и не скроллиться.
- Переключение между экранами только по тапу на кнопке, свайп не используется, что дает возможность комбинировать BottomNavigationView с вкладками. Но Google рекомендует подходить к такой комбинации с осторожностью, чтобы не дезориентировать пользователя.
- Можно закрашивать активный значок в основной цвет приложения, либо использовать белый или черный цвета, если нижняя панель навигации цветная. Не разукрашивайте значки в разные цвета.
- Текстовые метки должны быть максимально короткими и информативными, избегайте длинных надписей.
- Нажатие значка на нижней панели навигации должно привести пользователя прямо к связанному со значком окну, или обновить текущее окно. Значки не должны открывать меню или всплывающие окна. Кнопка «Назад» на устройстве не должна позволять перемещаться между экранами нижней панели навигации.
- Нижняя панель навигации может исчезать с экрана при прокрутке вверх и появляться при возвращении.
Ссылка на более подробную спецификацию есть в описании видео, а мы с вами переходим к практике — я покажу, как внедрить BottomNavigationView в приложение.
Для добавления поддержки нижней панели навигации в файл сборки уровня модуля нужно добавить такие зависимости:
Источник
Navigation Component-дзюцу, vol. 1 — BottomNavigationView
Два года назад на Google I/O Android-разработчикам представили новое решение для навигации в приложениях — библиотеку Jetpack Navigation Component . Про маленькие приложения уже было сказано достаточно, а вот о том, с какими проблемами можно столкнуться при переводе большого приложения на Navigation Component , информации практически нет.
В этой и следующих двух статьях я расскажу о кейсах, с которыми может встретиться разработчик, желающий опробовать Navigation Component в большом Android-приложении.
UPDATE 30.12.2020:
Выводы, сделанные в этой статье, некорректны, так как при настройке BottomNavigationView была допущена серьёзная ошибка, которая повлекла за собой все описанные в статье проблемы. Подробности — в последней статье цикла.
Это текстовая версия моего выступления в рамках серии митапов по Android 11 в Android Academy. Само выступление было на английском, статью пишу на русском. Кому удобнее смотреть – велкам.
В первой статье я расскажу о кейсах, связанных с BottomNavigationView , во второй – о кейсах с вложенными графами навигации, в третьей – про навигацию в многомодульных приложениях, дип линки, встраиваемые фрагменты и диалоги. Все три статьи — лонгриды, которые, однако, способны сэкономить много времени и вам, и вашей команде.
Disclaimer
Я сделал пример, по структуре навигации повторяющий основные моменты навигации соискательского приложения hh.ru, и выхватил ряд проблем, о которых и собираюсь рассказать. Я основательно поресёрчил практическую сторону вопроса, но, разумеется, рассмотрел далеко не все возможные кейсы.
Схема моего тестового приложения выглядит так:
В цикле статей мы разберём каждый переход, который описан на этой схеме, а также несколько кейсов, которые не поместились на картинку.
Кейсы с BottomNavigationView
Когда я только-только услышал про Navigation Component , мне стало интересно: как будет работать BottomNavigationView и как Google подружит несколько отдельных back stack-ов в разных вкладках. Два года назад с этим кейсом были некоторые проблемы, и я решил проверить, как там обстоят дела сегодня.
Если кратко — проблемы не пропали, но появился способ их обойти. И, поскольку нижняя навигация сейчас есть практически в каждом большом приложении, нужно разбираться.
Первый опыт
Я установил Android Studio 4.1 Beta (последнюю более-менее стабильную версию на тот момент) и попробовал шаблон приложения с нижней навигацией. Начало было многообещающим.
- Мне сгенерировали Activity в качестве контейнера для хоста навигации и нижней навигации
Я убрал «шумовые» атрибуты, чтобы было проще читать.
Стандартный ConstraintLayout , в который добавили BottomNavigationView и тэг для инициализации NavHostFragment -а ( Android Studio , кстати, подсвечивает, что вместо фрагмента лучше использовать FragmentContainerView ).
- Для каждой вкладки BottomNavigationView был создан отдельный фрагмент
Все фрагменты были добавлены в качестве отдельных destination-ов в общий граф навигации.
- А ещё в проект был добавлен файл-ресурс для описания меню BottomNavigationView
При этом я заметил, что идентификаторы элементов меню должны совпадать с идентификаторами destination-ов в графе навигации. Не самая очевидная связь между табами BottomNavigationView и фрагментами, но работаем с тем, что есть.
Пора запускать приложение
После создания приложения из шаблона я запустил его и сразу столкнулся с двумя проблемами.
Первая проблема: при переключении между вкладками их состояние не сохранялось.
Для проверки я добавил во вкладку Dashboard простенькую ViewModel со счётчиком. На гифке видно, как я переключаюсь со вкладки Home на вкладку Dashboard , увеличиваю счётчик до четырёх. После этого я переключился обратно на вкладку Home и вновь вернулся на Dashboard . Счётчик сбросился.
Баг с описанием этой проблемы уже два года висит в Issue Tracker-е. Чтобы решить её, Google-у потребовалось серьёзно переработать внутренности фреймворка Fragment -ов, чтобы поддержать возможность работать с несколькими back stack-ами одному FragmentManager -у. Недавно на Medium вышла статья Ian Lake, в которой он рассказывает, что Google серьёзно продвинулись в этом вопросе, так что, возможно, фикс проблемы с BottomNavigationView не за горами.
Вторая проблема – следствие первой. По принятым принципам навигации, когда вы нажимаете на уже выбранную вкладку BottomNavigationView, вы должны вернуться на первый фрагмент в стеке текущей вкладки. Но когда это происходит, состояние этого первого фрагмента сбрасывается, так как сам фрагмент пересоздаётся.
Для демонстрации этой проблемы я добавил на вкладку Dashboard кнопку, которая ведёт на следующий экран. На гифке видно, как я переключаюсь на вкладку Dashboard , увеличиваю счётчик до трёх, а затем перехожу на экран Graphic . Если я нажимаю на кнопку Back – то всё работает как надо, состояние вкладки не сбрасывается. Но если, находясь на экране Graphic , я ещё раз нажму на вкладку Dashboard , то после возврата на первый экран в стеке увижу, что его состояние сброшено.
«Не самое лучшее первое впечатление», – подумал я. И начал искать фикс.
У нас есть workaround
Решение этих проблем живёт в специальном репозитории Google-а с примерами работы с Architecture Components , в проекте NavigationAdvancedSample.
Большая часть фикса расположена в файле NavigationExtensions.kt. В самом проекте довольно много кода, поэтому я не буду его разбирать подробно, а вместо этого подсвечу основные моменты, которые относятся к решению проблем.
- Во-первых, для каждой вкладки вводится отдельный, независимый граф навигации
Соответственно, для примера BottomNavigationView с тремя вкладками у нас получится три отдельных файла навигации XML, в которых в качестве startDestination будут указаны первые фрагменты вкладок.
- Во-вторых, для каждой вкладки под капотом создаётся отдельный NavHostFragment , который будет связан с графом навигации этой вкладки
FragmentManager пока что не поддерживает работу с множеством back stack-ов одновременно, поэтому пришлось придумать альтернативное решение, которое позволило ассоциировать с каждым графом свой back stack. Им стало создание отдельного NavHostFragment -а для каждого графа. Из этого следует, что с каждой вкладкой BottomNavigationView у нас будет связан отдельный NavController .
- В-третьих, мы устанавливаем в BottomNavigationView специальный listener , который будет заниматься переключением между back stack-ами фрагментов
В прикреплённом кусочке кода мы видим, как при переключении между вкладками BottomNavigationView выполняется специальная транзакция в FragmentManager -е, которая прикрепляет фрагмент выбранной вкладки и отцепляет все остальные фрагменты. По сути, так мы и переключаемся между различными back stack-ами.
- В итоге метод настройки BottomNavigationView возвращает разработчику специальную LiveData , которая содержит в себе NavController выбранной вкладки. Этот NavController можно использовать, например, для обновления надписи на ActionBar -е
Метод для настройки BottomNavigationView вызывают в onCreate -е, когда Activity создаётся в первый раз, затем в методе onRestoreInstanceState , когда Activity пересоздаётся с помощью сохранённого состояния.
Кроме того, для работы фикса нужно, чтобы идентификаторы элементов меню, которое используется для инициализации табов BottomNavigationView , совпадали с идентификаторами графов навигации.
Опять же, не самая очевидная связь между этими элементами, зато работает.
После применения этого workaround-а первые две проблемы исчезли – теперь состояние вкладки сохраняется между переключениями вкладок.
Первая проблема решилась:
Адаптация workaround-а для фрагментов
Очень здорово, что workaround помог решить основную проблему с сохранением состояний, но этих фиксов мне было недостаточно. Пример из проекта NavigationAdvancedSample использовал в качестве контейнера нижней навигации Activity, мне же нужен был фрагмент.
Посмотрите внимательно на эту схему:
На ней можно увидеть, что пользователь начинает свой путь в приложении со Splash-экрана:
Google говорит, что Splash-экраны – зло, ухудшающее UX приложения. Тем не менее, Splash-экраны – суровая реальность большинства крупных Android-приложений. И если мы хотим использовать в нашем приложении Single Activity -архитектуру, то в качестве контейнера нижней навигации придётся использовать Fragment , а не Activity :
Я добавил вёрстку для фрагмента с нижней навигацией и перенёс настройку BottomNavigationView во фрагмент:
Я добавил в свой пример Splash-экран и дополнительную вкладку для BottomNavigationView . А чтобы пример стал ещё более походить на приложение для соискателей hh.ru, я также убрал из него ActionBar .
Для этого я поменял тему приложения с Theme.MaterialComponents.DayNight.DarkActionBar на Theme.MaterialComponents.DayNight.NoActionBar и убрал код для связки NavController -а с ActionBar -ом:
После всех манипуляций я включил режим Don’t keep activities, запустил свой пример и… получил краш при сворачивании приложения.
На гифке видно, как я запустил приложение, и после Splash-экрана показывается экран с нижней навигацией. После этого мы сворачиваем приложение и получаем краш.
В чём была причина? При вызове onDestroyView активный NavHostFragment пытается отвязаться от NavController -а. Так как мой фрагмент-контейнер с нижней навигацией никак не привязывал к себе NavController , который он получил из LiveData , метод Navigation.findNavController из onDestroyView крашил приложение.
Добавляем привязку NavController -а к фрагменту с нижней навигацией (для этого в Navigation Component-е есть утилитный метод Navigation.setViewNavController ), и проблема исчезает.
Но это ещё не всё. Не выключая режим Don’t keep activities, я попробовал свернуть, а затем развернуть приложение. Оно снова упало, но с другим неприятным исключением – IllegalStateException в FragmentManager – FragmentManager already executing transactions .
На гифке видно, как мы сворачиваем приложение, находясь на экране с нижней навигацией, затем пытаемся развернуть приложение обратно и получаем краш.
Краш происходит в методах, которые прикрепляют NavHostFragment к FragmentManager -у после их создания. Это исключение можно исправить при помощи костыля: обернуть методы attach-detach в Handler.post <> .
После добавления Handler.post приложение заработало, как надо.
Выводы по работе с BottomNavigationView
На этом с BottomNavigationView всё, на следующей неделе расскажу про кейсы с вложенными графами навигации.
Источник