Переключение между фрагментами android kotlin

Android — ViewPager2 — заменяем фрагменты на лету (программно)

Вдруг вам надо листать фрагменты через ViewPager2 и при этом подменять их динамически. Например, чтобы уйти «глубже» — пользователь из фрагмента «Главные настройки» переходит во фрагмент «Выбор языка». При этом новый фрагмент должен отобразиться на месте главного фрагмента. А потом пользователь еще и захочет вернуться обратно.

список Fragment-ов (например, разделы анкеты или многостраничный раздел настроек)

Kotlin (Java), Android собственно

Задача

Необходимо заменять фрагменты динамически — на месте n-ого фрагмента должен появится другой. При этом все остальные страницы не должны терять своего состояния.

Есть три фрагмента. Первый — список любимых тарелок, второй — экран с сообщениями, третий — настройки.

Пользователь на первом экране выбирает тарелку -> меняем первый фрагмент на фрагмент «Информация о тарелке»

Пользователь листает свайпами или выбирает через TabLayout третий экран — «Настройки», выбирает раздел «Выбор языка» -> меняем третий фрагмент на фрагмент «Выбор языка»

Теперь пользователь, не выходя из выбора языка, листает до первого фрагмента. Он должен увидеть информацию о тарелке, а не список любимых тарелок.

Итак, пытаемся сдружить ArrayMap и ViewPager2.

Результат

Дисклеймер (не читать, вода)

Пишу, потому что найти хороших решений по этой задаче не смог, хотя вроде пытался усердно.

Код местами специально упрощен — не люблю, когда в статьях добавляют десятки строк, не касающихся решения описываемой задачи, только ради соблюдения всех правил. Так что: строковые ресурсы — вынести, вью модели для фрагментов — добавить, пару уровней абстракций — ну вы поняли

Весь проект — https://github.com/IDolgopolov/ViewPager2_FragmentReplacer

Иногда буду использовать слово «страницы» — имею ввиду позицию, на которой отображается фрагмент во ViewPager. Например, «Список любимых тарелок», «Экран сообщений» и «Настройки» — три страницы. Фрагмент «Выбор языка» заменяет третью страницу.

Решение

Сначала ничего интересного, просто для общего ознакомления

Верстка

Интерфейсы

Опишем функции, которые нам понадобятся для смены фрагментов

Все фрагменты, добавляемые во ViewPagerAdapter (MyViewPager2Adapter описан ниже), будут наследоваться от BaseFragment.

Определим в нем переменные для хранения:

pageId — уникальный номер страницы. Может быть любым числом, но главное — уникальным и большим, чем у вас позиций страниц (pageId > PAGE_COUNT — 1), иначе будут баги из-за метода getItemId()

pagePos — номер странице, на которой будет отображаться фрагмент во ViewPager (начиная с 0, естественно)

fragmentReplacer — ссылка на ViewPagerAdapter (MyViewPager2Adapter реализует FragmentReplacer)

Связь активити и ViewPager2

Адаптер для ViewPager2 — всё здесь

В этом классе будут реализован интерфейс FragmentReplacer и переопределены несколько классов FragmentStateAdapter. Это и позволит менять фрагменты на лету.

Весь код класса, который ниже разбираю подробно

В первую очередь переопределим четыре метода FragmentStateAdapter:

Теперь две функции, которые позволяют подменять фрагменты

Пример использования

Все готово — у каждого фрагмента есть ссылка на адаптер, значит есть возможность заменить фрагмент на любой странице, а самое главное (наиболее используемое на практике) — есть возмжность заменить самого себя.

Например, фрагмент, по умолчанию, отображаемый на первой странице:

Мысли в слух

Аккуратно, если фрагменты тяжелые, — надо задуматься об очищении mapOfFragment.

Возможно, стоит хранить Class вместо BaseFragment. Но тогда придется инициализировать их каждый раз в createFragment(). Меньше памяти, больше времени. Что думаете?

Источник

Полный список

— динамически работаем с фрагментами

Размещать статические фрагменты мы уже умеем. Но, ясно дело, что гораздо интереснее работать с ними динамически. Система позволяет нам добавлять, удалять и заменять фрагменты друг другом. При этом мы можем сохранять все эти манипуляции в BackStack и кнопкой Назад отменять. В общем, все удобно и красиво.

Создадим простое приложение с двумя фрагментами, которое будет уметь:

— добавлять первый фрагмент
— удалять первый фрагмент
— заменять первый фрагмент вторым фрагментом
— переключать режим сохранения в BackStack операций с фрагментами

Project name: P1051_FragmentDynamic
Build Target: Android 4.1
Application name: FragmentDynamic
Package name: ru.startandroid.develop.p1051fragmentdynamic
Create Activity: MainActivity

В strings.xml добавим строки:

Создаем фрагменты. Как мы помним из прошлого урока, для этого нам нужны будут layout-файлы и классы, наследующие android.app.Fragment

Все почти аналогично прошлому уроку, только убрали вызовы кучи lifecycle методов с логами.

Рисуем основное Activity.

Три кнопки для добавления, удаления и замены фрагментов. Чекбокс для включения использования BackStack. И FrameLayout – это контейнер, в котором будет происходить вся работа с фрагментами. Он должен быть типа ViewGroup. А элементы Fragment, которые мы использовали на прошлом уроке для размещения фрагментов, нам не нужны для динамической работы.

В onCreate создаем пару фрагментов и находим чекбокс.

В onClick мы получаем менеджер фрагментов с помощью метода getFragmentManager. Этот объект является основным для работы с фрагментами. Далее, чтобы добавить/удалить/заменить фрагмент, нам необходимо использовать транзакции. Они аналогичны транзакциям в БД, где мы открываем транзакцию, производим операции с БД, выполняем commit. Здесь мы открываем транзакцию, производим операции с фрагментами (добавляем, удаляем, заменяем), выполняем commit.

Итак, мы получили FragmentManager и открыли транзакцию методом beginTransaction. Далее определяем, какая кнопка была нажата:

если Add, то вызываем метод add, в который передаем id контейнера (тот самый FrameLayout из main.xml) и объект фрагмента. В итоге, в контейнер будет помещен Fragment1

если Remove, то вызываем метод remove, в который передаем объект фрагмента, который хотим убрать. В итоге, фрагмент удалится с экрана.

если Replace, то вызываем метод replace, в который передаем id контейнера и объект фрагмента. В итоге, из контейнера удалится его текущий фрагмент (если он там есть) и добавится фрагмент, указанный нами.

Далее проверяем чекбокс. Если он включен, то добавляем транзакцию в BackStack. Для этого используем метод addToBackStack. На вход можно подать строку-тэг. Я передаю null.

Ну и вызываем commit, транзакция завершена.

Давайте смотреть, что получилось. Все сохраняем, запускаем приложение.

появился первый фрагмент.

Еще раз добавим первый фрагмент – жмем Add. И жмем Replace

первый фрагмент заменился вторым.

Жмем кнопку Назад. Приложение закрылось, т.к. все эти операции с фрагментами не сохранялись в BackStack. Давайте используем эту возможность.

Снова запускаем приложение и включаем чекбокс add to Back Stack

Выполняем те же операции: Add, Remove, Add, Replace. У нас добавится первый фрагмент, удалится первый фрагмент, добавится первый фрагмент, заменится вторым. В итоге мы снова видим второй фрагмент. Теперь жмем несколько раз кнопку Назад и наблюдаем, как выполняются операции, обратные тем, что мы делали. Когда транзакции, сохраненные в стеке закончатся, кнопка Назад закроет приложение.

Т.е. все достаточно просто и понятно. Скажу еще про пару интересных моментов.

Я в этом примере выполнял всего одну операцию в каждой транзакции. Но, разумеется, их может быть больше.

Когда мы удаляем фрагмент и не добавляем транзакцию в BackStack, то фрагмент уничтожается. Если же транзакция добавляется в BackStack, то, при удалении, фрагмент не уничтожается (onDestroy не вызывается), а останавливается (onStop).

В качестве самостоятельной работы: попробуйте немного изменить приложение и добавлять в один контейнер сразу два фрагмента. Возможно, результат вас удивит )

На следующем уроке:

— рассмотрим взаимодействие между Activity и ее фрагментами

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

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

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

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

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

Источник

Android Fragment Result Listener

В Android передача данных между фрагментами может осуществляться разными способами: передача через родительскую Activity, используя ViewModel или даже Fragments API. Fragment Target API с недавних пор получил статус Deprecated и вместо него Google рекомендует использовать Fragment result API.

Что такое Fragment result API? Это новый инструмент от Google который позволяет передавать данные между фрагментами по ключу. Для этого используется FragmentManager, который в свою очередь реализует интерфейс FragmentResultOwner. FragmentResultOwner выступает в качестве центрального хранилища для данных, которые мы передаем между фрагментами.

Читайте также:  Через чего обновлять android

Как это работает?

Как упоминалось выше, наш FragmentManager реализует интерфейс FragmentResultOwner, который хранит в себе ConcurrentHashMap . Эта HashMap хранит наши Bundle-ы по строковому ключу. Как только один из фрагментов подписывается (или уже подписан) то он получает результат по тому самому ключу.

Что важно знать:

  • Если какой-либо фрагмент подписывается на результат методом setResultFragmentListener() после того, как отправляющий фрагмент вызовет setFragmentResult() , то он немедленно получит результат
  • Каждую связку “Key + Result (Bundle)“ фрагмент получает только 1 раз
  • Фрагменты которые находятся в бек стеке получат результат только после того как перейдут в состояние STARTED
  • После того как фрагмент перейдет в состояние DESTROYED мы больше не сможем подписываться на ResultListener

Как это выглядит в коде?

Передача данных

Для передачи данных в другой фрагмент нам необходимо вызвать метод:

В параметры метода мы кладем ключ, который и будет нашим идентификатором для получения данных и сам Bundle. Этот Bundle будет содержать в себе передаваемые данные.

Получение данных

Для получения данных через FragmentManager мы регистрируем наш FragmentResultListener и задаем ключ по которому мы будем получать данные. Тот самый ключ который мы указывали в методе FragmentManager.setFragmentResult()

Здесь мы видим 2 аргумента: key: String и bundle: Bundle.
Первый — это тот самый ключ, по которому мы передаем сюда данные. Второй — Bundle, в котором лежат переданные данные.

Parent Fragment Manger

Выбор FragmentManager-а для передачи данных между фрагментами зависит от принимающего фрагмента:

  • Если оба фрагмента находятся в одном и том же FragmentManager (например оба фрагмента находятся в Activity), то мы должны использовать родительский FragmentManager, который хранит в себе Activity
  • Если у нас один фрагмент вложен в другой фрагмент, то для передачи данных мы используем childFragmentManager (он же родительский фрагмент для принимающего фрагмента)

Важно понимать, что наш FragmentResultListener должен находиться в общем для двух фрагментов FragmentManager-е.

Тестирование

Для тестирования отправки/получения данных через FragmentResultListener, мы можем использовать FragmentScenario API, который предоставляет нам все преимущества тестирования фрагментов в изоляции.

Передача данных

Как мы можем протестировать, что наш фрагмент корректно отправляет данные через родительский FragmentManager? Для этого нам необходимо внутри теста отправить результат и проверить, что наш FragmentResultListener получил корректные данные:

Получение данных

Для проверки корректности получения данных мы можем симулировать отправку данных, используя родительский FragmentManager. Если в отправляющем фрагменте корректно установлен FragmentResultListener мы должны получить корректные данные проверяя сам листенер или последствие их получения.

Вывод

В данный момент FragmentResultListener находится в альфе, а это значит что возможно еще будут изменения со стороны Google. Но уже сейчас видно, что это достаточно крутой инструмент, для передачи данных между фрагментами, не создавая дополнительных интерфейсов и классов. Единственным нюансом остается, пожалуй то, что не совсем понятно, как и где лучше хранить ключи где, но это не кажется таким уж большим минусом.

Для того чтоб получить возможность использовать FragmentResultListener нам нужно подключить в зависимостях версию фрагментов 1.3.0-alpha04 или новее:

Источник

Переключение между фрагментами android kotlin

Навигация в приложении

Для работы с навигацией между экранами в Android-приложении применяется библиотека Navigation Component. Она входит в набор Android Jetpack и помогает реализовать навигацию, от простых нажатий на кнопки до более сложных, таких как панели приложений (action bars) и панель навигации (navigation drawers).

Библиотека предоставляет ряд преимуществ:

  • Корректная обработка кнопок «Вверх» и «Назад» по умолчанию
  • Поведение по умолчанию для анимации и переходов.
  • Реализация шаблонов навигации пользовательского интерфейса (таких, как navigation drawer).
  • Безопасность типов при передаче информации во время навигации.
  • Инструменты Android Studio для визуализации и редактирования navigation flow приложения.

В качестве примера будет использоваться готовый проект, в котором уже есть все необходимые экраны, их макеты и исходные коды. Необходимо будет лишь разобраться с навигацией между экранами.

Тестовое приложение представляет собой мини-викторину. Оно будет иметь несколько экранов:

  • Стартовый экран с логотипом и кнопкой «Play».
  • Экран с вопросами и вариантами ответа.
  • Экран с кнопкой «Try Again» в случае ошибки.
  • Экран с кнопкой «Next Match» в случае успешного прохождения теста.
  • Экран «About» с информацией о приложении.
  • Экран «Rules» с текстом правил приложения.

Стартовый проект с приложением содержит все эти экраны, но не содержит связей между ними, их создание будет рассмотрено далее. Каждое описанный экран — это фрагмент. Фрагмент — особый компонент системы Android. Прежде чем начать разбираться с навигацией по фрагментам, нужно разобраться с тем, что это такое.

Фрагмент (класс Fragment ) предоставляет часть пользовательского интерфейса в активности ( Activity ). Активности могут содержать как несколько фрагментов внутри себя, так и один фрагмент. Обычно несколько фрагментов в одной активности располагают при разработке приложения для планшетов, когда для меню навигации и для основного контента выделяются отдельные фрагменты, располагающиеся на экране одновременно.

Фрагменты имеют свой отдельный жизненный цикл и самостоятельно отдельно от активностей занимаются обработкой событий ввода.

Обычно фрагменты имеют собственные макеты (layouts), описывающие элементы пользовательского интерфейса. Если в активностях инициализация и установка макетов выполняется в методе onCreate() , то в фрагментах — в методе onCreateView() общем для всех фрагментов. Вместо метода setContentView() для установки макета здесь используется метод inflate() , принимающий на вход ссылку на ресурс с макетом, объект container , являющийся родительским видом и булево значение, определяющее нужно ли новый макет привязать к корневому (в большинстве случаев используется false ).

Для добавления фрагмента в активность необходимо описать тег в макете активности.

Существует подклассы фрагментов: ListFragment , DialogFragment , PreferenceFragment , WebViewFragment и др., упрощающие реализацию и работу с конкретными типами окон: списками, диалоговыми окнами, настройками и веб-содержимым, соответственно.

Именно фрагменты чаще используются в качестве компонентов, описывающих экраны приложения, располагаясь в одной или паре активностей, в зависимости от задач приложения. Фрагменты позволяют настраивать боковые панели навигации (navigation drawer) и ActionBar-меню. Фрагменты упрощают вопрос навигации и передачи данных между экранами приложения. Также создание фрагментов — наиболее выгодно с точки зрения траты ресурсов системы, чем создание активностей.

Перейдем к вопросам реализации навигации между экранами приложения.

Прежде всего необходимо выделить три основные принципа навигации:

  1. Приложение всегда должно иметь стартовый экран. Стартовый экран — тот, который показывается при запуске приложения.
  2. Пользователь должен всегда иметь возможность вернуться назад. Это подразумевает как возврат на один экран назад с помощью системной кнопки «Назад», так и с помощью аналогичной кнопки в Action Bar.
  3. Системная кнопка «Назад» и кнопка «Назад» на Action Bar должны работать аналогичным образом. Исключение — нажатие на системную кнопку на стартовом экране сворачивает приложение и возвращает пользователя на рабочий экран Android.

Сперва необходимо скачать и собрать стартовый проект trivia-starter-code. Стартовый проект уже содержит почти все необходимые фрагменты (кроме одного), все необходимые макеты, создавать их не нужно. Необходимо сосредоточиться исключительно на навигации.

Добавление нового фрагмента

Единственный фрагмент, которого не хватает в стартовом проекте — это фрагмент с начальным экраном приложения TitleFragment . Для его создания необходимо выполнить следующие шаги:

  1. File -> New -> Fragment -> Fragment (Blank).
  2. Fragment Name: TitleFragment .
  3. Убрать галочки «Create layout XML?», «Include fragment factory methods?» и «Include interface callbacks?» Макет для этого фрагмента уже есть в стартовом проекте.
  4. Нажать «Finish».

Далее необходимо заменить код, сгенерированный автоматически для настройки макета фрагмента ( return TextView(activity).apply ), на использование метода DataBindingUtil.inflate() . Это позволит нам и инициализировать макет для фрагмента и использовать в дальнейшем Data Binding.

Определение метода onCreateView() должно выглядеть следующим образом:

И в конце необходимо поместить фрагмент с начальным экраном на MainActivity , чтобы его можно было отобразить. В файл activity_main.xml :

После запуска приложения на экране отобразится содержимое TitleFragment — начальный экран с логотипом и кнопкой «Play». По нажатию на кнопку ничего не выполняется.

Добавление перехода от одного экрана к другому

Для реализации непосредственно навигации между экранами необходимо добавить зависимость от библиотеки Navigation Component, и создать файл с графом навигации.

1. Добавление зависимости Navigation Components:

Для добавления зависимости необходимо сперва добавить переменную version_navigation в блок ext в Gradle-файл проекта. Далее эта переменная будет использоваться для добавления зависимости конкретной версии, избавляя от дублирования номера версии в нескольких местах.

Добавление непосредственно зависимостей в Gradle-файл модуля app :

2. Добавление графа навигации:

Граф навигации — файл с описанием экранов приложения и связей между ними в формате XML.
Для создания файла навигации необходимо кликнуть правой кнопкой на модуль app и выбрать New -> Android resource file.
В качестве имени файла ресурса необходимо указать navigation.xml , а также выбрать тип Navigation .
Когда файл будет создан, его можно будет найти в каталоге res/navigation .

Читайте также:  Аналоги диабло для андроид

3. Замена TitleFragment на NavHostFragment в активности:

Для реализации навигации также необходимо добавить NavHostragment — контейнер, который будет информацию о пунктах назначения на графе навигации. Класс NavHostragment является стандартным. Чтобы его использовать, необходимо в описании макета activity_main.xml заменить использование TitleFragment на NavHostFragment .

Параметр name ссылается на полное имя класса NavHostFragment .
Параметр navGraph ссылается на ресурс с описанием графа навигации, откуда класс NavHostFragment будет брать информацию о связях между фрагментами с экранами.
Параметр defaultNavHost определяет, что этот контейнер навигации является контейнером по умолчанию и именно к нему будет применено использование системной кнопки «Назад».

4. Добавление фрагментов на граф навигации:

Для редактирования навигации используется редактор навигации. Он так же как и редактор макетов имеет два режима (две вкладки): Design и Code. Первый позволяет использовать элементы интерфейса IDE для редактирования графа навигации, второй предоставляет доступ к исходному XML-файлу графа. Кроме этого еще есть вкладка Split, которая делит рабочую область на две части: в одной код, во второй — графическое представление.

Для добавления фрагмента на граф навигации необходимо в режиме Design необходимо нажать на кнопку + в верхнем левом углу и среди макетов выбрать необходимый, например, fragment_title . Миниатюра фрагмента появится в рабочей зоне графа навигации.
Далее необходимо сделать TitleFragment стартовым. Для этого необходимо нажать на кнопку Assign start destination, когда фрагмент выбран, либо указать id фрагмента в качестве атрибута Start Destination ( app:startDestination в XML-коде) для самого графа навигации.

Если запустить приложение, то будет отображен TitleFragment , находящийся внутри NavHostFragment , который в свою очередь располагается внутри одной единственной активности.

Далее необходимо добавить еще один фрагмент, чтобы установить переход от начального экрана к следующему. Добавим на граф навигации fragment_game . Миниатюра макета также появляется в рабочей зоне графа навигации.

Для установки перехода необходимо протянуть связь от фрагмента TitleFragment (индикатора в виде «кружочка») к GameFragment , и установить обработчик нажатию на кнопку «Play» на TitleFragment . По нажатию на эту кнопку должен выполниться переход к основному экрану игры GameFragment .

Реализация обработчика будет выглядеть следующим образом:

Статический метод Navigation.findNavController() служит для получения экземпляра класса NavController , который является контроллером для графа навигации, предоставляет различную информацию о графе навигации и позволяет взаимодейстовать с ним. Так NavController имеет метод navigate() , выполняющий переход от одного экрана к другому по уникальному идентификатору, который генерируется автоматически при создании связи. Например, идентификатор action_titleFragment_to_gameFragment означает переход от titleFragment к gameFragment (эти идентификаторы описаны в элементах на графе навигации).

Помимо начального и основного игрового экрана приложение имеет еще экран победы GameWonFragment и экран поражения GameOverFragment . Их также необходимо добавить на граф навигации, установить переход к ним от GameFragment . Затем в классе GameFragment в методе onCreateView() необходимо добавить код для выполнения перехода к фрагментам в зависимости от определенных условий (места для вставки кода выделены комментариями).

Теперь после запуска приложения и успешного прохождения теста будет выполнен переход к GameWonFragment , а в случае ошибки GameOverFragment .

Манипуляции со стеком фрагментов

Каждый новый открытый фрагмент помещается на стек фрагментов. Например, если пользователь прошел по всем экранам приложения от начального до экрана «Gave Over», то стек фрагментов содержит TitleFragment , GameFragment и GameOverFragment . Последовательность именно такая.

Если пользователь на экране «Game Over» нажмет на системную кнопку «Назад», то вернется к экрану с игрой, где будет написан последний вопрос, а это является некорректным поведением. Для пользователя логичным было бы попасть на начальный экран, а для этого необходимо удалить фрагмент GameFragment из стека фрагментов. Такое поведение можно реализовать следующим образом.

1. Настройка поведения системной кнопки «Назад»:

Чтобы нажатие на системную кнопку «Назад» выполняло переход от GameOverFragment к TitleFragment , необходимо настроить атрибут Pop Behavior. Для этого нужно выбрать уже созданную связь между GameFragment и GameOverFragment , на панели атрибутов выбрать Pop Behavior -> popUpTo -> gameFragment и установить галочку popUpToInclusive. Настройка «поведения» определяет к какому фрагменту необходимо вернуться назад по выбранной связи (все фрагменты выше выбранного на стеке будут удалены), а галочка popUpToInclusive говорит, что сам фрагмент GameFragment тоже нужно удалить со стека (это вернет пользователя на фрагмент TitleFragment ).

Аналогичное поведение необходимо реализовать и возврата с фрагмента GameWonFragment к TitleFragment .

2. Добавление обработчика для кнопок на GameOverFragment и GameWonFragment :

На экранах «Game Over» и «Congratulations» есть кнопки «Try Again» и «Next Match» соответственно. Эти кнопки должны позволять пользователю начать игру заново, т.е. возвращать пользователя к предыдущему экрану с вопросами GameFragment .

Для этого необходимо протянуть новую связь от GameOverFragment к GameFragment и на панели атрибутов установить Pop To -> titleFragment. Здесь указывается возврат к titleFragment для того, чтобы в любой момент, когда показан фрагмент GameOverFragment можно было вернуться к TitleFragment . Иначе, на стеке может казаться множество GameOverFragment и по кнопке «Back» возврат будет происходить именно к ним.
Затем необходимо перейти в класс GameOverFragment и установить обработчик кнопки «Try Again».

Аналогично необходимо создать связь между GameWonFragment и GameFragment , настроить Pop Behavior и установить обработчик нажатия на кнопку «Match Again».

3. Добавление кнопки «Назад» на Action Bar:

Кроме системной кнопки «Назад» Android поддерживает еще и кнопку «Назад», отображающуюся на Action Bar — панели сверху приложения. Для ее добавления необходимо внести изменения в MainActivity . Необходимо получить экземпляр NavController и использовать его для установки Action Bar:

Далее необходимо переопределить метод активности onSupportNavigateUp() :

Метод onSupportNavigateUp() определяет, что именно должно быть выполнено при навигации с помощью кнопки «Назад». В данном случае это навигация по стеку с помощью экземпляра NavController .

Если запустить приложение, можно убедиться, что кнопка «Назад» на Action Bar отображается и работает она аналогично системной кнопке «Назад.

Action Bar может содержать собственное меню. Обычно меню помечается кнопкой в виде трех вертикальных точек.
В случае приложения «Trivia» меню будет содержать один пункт «About», открывающий отдельный экран с информацией о приложении. Фрагмент AboutFragment уже создан.

1. Добавление фрагмента AboutFragment на граф навигации:

Для добавления возможности перехода к экрану «About» необходимо сперва добавить его на граф навигации.

2. Создание нового ресурса menu :

Конфигурация меню описывается в специальных ресурсах menu . Для его добавления необходимо кликнуть правой кнопкой по модулю app , выбрать New -> Android Resource File. Далее необходимо указать тип Menu и имя файла, например, overflow_menu и кликнуть «Ok». В результате будет создан файл overflow_menu.xml и помещен в каталог res/menu .

В меню необходимо добавить новый элемент Menu Item.
На панели атрибутов указать:

  • ID: aboutFragment — совпадает с id фрагмента, к которому необходимо перейти при выборе данного пункта меню (ВАЖНО, чтобы эти идентификаторы совпадали),
  • title: @string/about — ссылка на строковый ресурс.

3. Добавление меню на Action Bar фрагмента TitleFragment :

Для добавления меню на Action Bar фрагмента TitleFragment необходимо сперва добавить вызов метода setHasOptionsMenu(true) внутри метода onCreateView() . Вызов метода определяет, что данный фрагмент в принципе может отображать меню.

Для инициализации и установки меню из файла ресурса необходимо переопределить метод фрагмента onCreateOptionsMenu() и описать в нем загрузку меню из файла overflow_menu .

4. Реализация обработчика нажатия на элемент меню:

Чтобы нажатие на элемент меню «About» имело эффект, необходимо реализовать обработчик нажатия.
Для этого требуется переопределить метод фрагмента onOptionsItemSelected() .

Метод NavigationUI.onNavDestinationSelected() выполняет навигацию к фрагменту по идентификатору меню. Именно для этого идентификаторы фрагмента и меню, по которому к фрагменту нужно перейти, важно указывать одинаковыми.

По сути фрагмент AboutFragment имеет в графе навигации идентификатор aboutFragment . Добавленный пункт меню «About» имеет аналогичный идентификатор и метод NavigationUI.onNavDestinationSelected() , принимающий на вход элемент меню ( MenuItem ) и экземпляр NavController сопоставляет эти идентификаторы и выполняет переход, если это возможно. Если переход невозможен, то метод вернет false и будет вызвана родительская реализация метода super.onOptionsItemSelected(item) .

Иногда перед разработчиком может встать задача передачи данных между экранами приложения и для этого в Android есть удобный механизм. SafeArgs — это Gradle-плагин, позволяющий реализовать передачу данных между фрагментами.

Далее будет рассмотрено добавление SafeArgs для простейшей задачи — отображения текстового сообщения об общем числе вопросов и числе правильных ответов на GameWonFragment .

Читайте также:  Поддержка 4g android one

1. Добавление зависимости SafeArgs:

Сперва необходимо добавить зависимость от библиотеки SafeArgs в Gradle-файл проекта (не модуля app ).

Затем необходимо включить плагин в Gradle-файл модуля app .

После включения плагина необходимо очистить и пересобрать проект, чтобы компилятор сгенерировал «Directions»-классы, содержащие информацию о навигации между фрагментами (по типу идентификаторов action_gameFragment_to_gameWonFragment ), а также позволяющие организовать передачу данных между фрагментами.

2. Использование «Directions»-классов:

После того, как «Directions»-классы сгенерированы, можно заменить использование ссылок на идентификаторы навигации на использование этих классов.

По такому же принципу выполняется замена в остальных местах.

3. Добавление аргументов в редакторе навигации:

Далее необходимо описать для GameWonFragment данные, которые он должен получать. В данном случае — это общее число вопросом numQuestions и число правильных ответов numCorrect . Оба числа целочисленные ( integer ).

Для добавления описания параметров необходимо перейти в редактор навигации, выбрать gameWonFragment и добавить аргументы в разделе «Arguments» панели «Attributes».

После этого сборка проекта будет невозможно, поскольку вызов GameFragmentDirections.actionGameFragmentToGameWonFragment() будет требовать входных параметров, которые только что были описаны.

4. Передача данных фрагменту:

Чтобы передать данные фрагменту, необходимо передать их в вызов GameFragmentDirections.actionGameFragmentToGameWonFragment() .

Параметры numQuestions и questionIndex определяют общее число вопросов и индекс текущего вопроса, а в случае с выигрышем — это и число правильных ответов.

Теперь проект успешно собирается, данные передаются, но никак не принимаются и не обрабатываются.

5. Обработка полученных данных:

Переданные в GameWonFragment данные отобразим на экране с помощью Toast -сообщения. Для этого необходимо воспользоваться вызовом GameWonFragmentArgs.fromBundle(arguments) для получения передаваемых данных.
«Args»-класс GameWonFragmentArgs генерируется, так же как и «Directions»-классы, автоматически. Он предоставляет доступ к хранилищу Bundle куда сохраняются передаваемые данные.
Далее для получения конкретных значений необходимо обратиться к данным по именам ранее переданных переменных.

Таким образом при открытии GameWonFragment на экране будет отображено Toast -сообщение с полученными numCorrect и numQuestions .

Еще одним из основных элементов для навигации между экранами является боковое меню, также оно называется Navigation Drawer.
Далее будут рассмотрены шаги по добавлению бокового меню.

1. Добавление зависимости от библиотеки Material:

Navigation Drawer является частью библиотеки Material, поэтому ее необходимо включить в Gradle-файл:

2. Добавление XML-файла бокового меню:

Далее необходимо добавить XML-файл ресурса с описанием элементов бокового меню, аналогично тому, как это было сделано для ActioBar-меню. Файл будет называться navdrawer_menu.xml и будет содержать один элемент «About». Элемент меню должен иметь следующие атрибуты:

  • ID: aboutFragment — совпадает с идентификатором фрагмента, который должен быть открыт,
  • Name: @string/about — ссылка на строковый ресурс,
  • Icon: @drawable/android — ссылка на ресурс с изображением, ресурс уже содержится в проекте.

3. Добавление бокового меню на макет MainActivity :

Для возможность использования Navigation Draver необходимо добавить компонент DrawerLayout в активность и поместить внутрь этого компонента весь контент активности.

И затем добавить компонент NavigationView сразу после определения LineatLayout , внутри которого находится фрагмент.

Параметр headerLayout определяет какой макет использовать в качестве заглавной части меню.
В параметре menu задается ссылка на ресурс с меню, которое необходимо загрузить в NavigationView .

4. Инициализация и настройка Navigation Drawer:

Для инициализации и настройки работы Navigation Drawer необходимо сперва создать поле drawerLayout и инициализировать его в методе onCreate() :

Для отображения кнопки бокового меню на Action Bar необходимо добавить экземпляр drawerLayout в качестве третьего параметра вызова NavigationUI.setupActionBarWithNavController() :

Для добавления возможности навигации, т.е. перехода к экрану «About» при нажатии на соответствующие элемент бокового меню, необходимо добавить вызов NavigationUI.setupWithNavController() с передачей NavController и элемента NavigationView в качестве параметров:

Для того, чтобы корректно обрабатывалось и открытие бокового меню и кнопка «Назад», которая отображается при переходе к следующим за начальным экранам, необходимо добавит в обработчике onSupportNavigateUp() в вызов NavigationUI.navigateUp() параметры navController и drawerLayout .

В этом случае drawerLayout будет использоваться, если пользователь находится на последнем фрагменте в стеке. А navController будет использоваться для навигации в остальных случаях.

Интент — это объект, содержащий в себе запрос на выполнение некоторого действия.
Интент может включать в себя следующие компоненты:

  • действие,которое необходимо выполнить (обязательный компонент);
  • набор категорий, позволяющих группировать действия;
  • URI, идентифицирующий данные, над которыми необходимо выполнить действие;
  • дополнительные параметры (extras), необходимые для выполнения действия.

Наиболее распространённый сценарий использования интентов — запуск другой активности. При этом это может быть как запуск активности собственного приложения, так и стороннего, например, открытие галереи, приложения почты или камеры и т.д.

Существует два вида интентов: явные (explicit) и неявные (implicit).

Explicit-интенты используются для перехода к активности в рамках собственного приложения. Они требуют указания полного имени класса для выполнения действия

Первый параметр в конструкторе класса Intent — ссылка на контекст (в данном случае на активность, из которой выполняется вызов).
Второй параметр — полное имя класса второй активности, которую необходимо открыть.

Также при переходе к другой активности можно поместить в интент некоторые данные.

Для получения значения из интента внутри SecondActivity необходимо вызвать:

Implicit-интенты используются для открытия сторонних активностей сторонних приложений и выполнения каких-либо действий.
Примеры: открытие приложения почты для написания нового письма и передача в него определенного текста; открытие приложения камеры для создания снимка и получения его в рамках приложения; открытие браузера с сайтом по определенной ссылке.

Для создания implicit-интента необходимо указать три параметра:

  • action — действие, которое необходимо выполнить,
  • category — категория компонента (активности), который должен выполнить действие интента,
  • data — передаваемые данные.

Примеры констант ACTION:

  • ACTION_CALL — инициализирует обращение по телефону.
  • ACTION_SEARCH — запускает активность для выполнения поиска.
  • ACTION_SEND — запускает активность экран для отправки данных, указанных в поле data интента. Например, диалог для выбора приложения, в которое необходимо использовать в качестве «отправителя».
  • ACTION_VIEW — Наиболее распространённое общее действие. Используется для отображения какой-либо активности. Например, для открытия браузера.

Примеры констант CATEGORY:

  • BROWSABLE — активность может быть вызвана браузером, чтобы отобразить «ссылочные»» данные, например, изображение или почтовое сообщение.
  • LAUNCHER — активность может быть главной активностью, использующейся для запуска приложения в системе. Такую категорию имеет главная активность каждого приложения в Android.
  • HOME — активность отображает Home Screen, домашний экран, который пользователь видит после включения устройства и загрузки системы, или когда нажимает клавишу «HOME».

Пример ниже демонстрирует создание интента для открытия ссылки и запуск активности, которая подойдет для выполнения действия данного интента. При наличии нескольких браузеров, пользователю будет предложено выбрать один из них.

Следующий пример демонстрирует создание интента для отправки некоторого текста. В качестве действия (action) указывается ACTION_SEND , что при вызове startAction() позволит показать пользователю окно с возможностью выбора приложения для отправки сообщения (почтовые клиенты или мессенджеры).
Сообщение сохраняется в интенте под ключом EXTRA_TEXT . Это стандартный ключ системы Android, именно он используется приложениями, которые могут принимать сообщения извне для дальнейшей отправки (EXTRA_TEXT).

Добавление функции «Share» с использованием интента

Для демонстрации работы implicit-интентов будет добавлен функционал «Поделиться» с возможностью отправки данных, через сторонние приложения типа почтового клиента или мессенджера. В качестве сообщения будет использоваться добавленное ранее сообщения о количестве правильных ответов и общем числе вопросов.

Кнопка «Share» будет добавлена в ActionBar-меню GameWonFragment . В проекте уже есть файл winner_menu.xml , содержащий описание меню.

1. Настройка отображения меню на GameWonFragment :

2. Добавление метода для создания интента:

Для создания интента добавим метод getShareIntent() . Метод будет получать экземпляр Bundle с переданными данными ( numCorrect и numQuestions ) и создавать интент с действием ACTION_SEND , текстовым типом данных и строкой с самими данными.

3. Обработка нажатия на меню «Share»:

Для обработки нажатия необходимо переопределить метод onOptionsItemSelected и по нажатию на пункт меню «Share» вызывать системную активность, передавая ей созданный интент.

После запуска приложения можно убедиться, что по нажатию на пункт меню «Share» запускается системная активность, предлагающая выбрать приложение для отправки текстовых данных.

Если убрать установку типа «text/plain» для интента и запустить приложение снова, то после нажатия на пункт меню «Share» приложение упадет с сообщением о том, что активность для данного действия не найдена.

4. Добавление проверки на доступность активности:

Для того, чтобы решить проблему с падением, необходимо добавить проверку на доступность существующей активности для созданного интента. Если активности необходимой для интента нет в системе, то пункт меню «Share» логично будет скрыть.

Если запустить приложение, можно убедиться, что пункт меню «Share» не отображается на экране GameWonFragment .

Источник

Оцените статью