Fragment что это android
Fragment (Фрагменты)
Фрагменты немного пугают новичков. Постараюсь объяснить как можно проще, чтобы отдельные фрагменты пазла сложились в единую картину.
Зачем?
Создатели операционной системы Android оказались недальновидными разработчиками. Не посоветовавшись с котами, они разработали систему под маленькие экраны телефонов. Но котам было неудобно пользоваться такими экранами, и тогда придумали планшеты.
Старые программы прекрасно на них запускались, но обнаружилось несколько недостатков. На больших экранах интерфейс выглядел не слишком элегантно, появились большие пустые пространства. И тогда возникла идея объединить два отдельных экрана из смартфона в один экран на планшете. Это самый классический пример применения фрагмента. По сути, это костыль. Возможно, если бы сразу подумали головой, то придумали бы более элегантное решение. Но теперь поздно пить «Боржоми», будем использовать предложенную концепцию.
Фрагменты были представлены в API 11 (Android 3.0), но в целях совместимости была написана специальная библиотека Android Support library для старых устройств. Долгое время существовало два класса Fragment: для новых устройств и для старых устройств. Названия методов и классов были очень похожи, и разработчики часто путались, смешивая в одном проекте два разных несовместимых класса. Спустя некоторое время решили отказаться от странного разделения, класс для новых устройств признали устаревшим, а класс из библиотеки поддержки старых устройств сменил своё полное имя и вошёл в состав AndroidX.
При желании можно было продолжить писать приложения в старом стиле, отслеживая размеры экрана. Но такой код получится слишком сложным. Пришлось бы писать один код для переключения от одной активности к другой при использовании смартфона и другой код, когда взаимодействие между объектами происходит на одном экране в планшете. Чтобы устранить это противоречие, были придуманы фрагменты. Хотя там тоже придётся писать много кода.
В составе активности есть специальный менеджер фрагментов, который может контролировать все классы фрагментов и управлять ими. О нём позже.
Фрагмент может иметь свою разметку, а может обойтись без неё. Также у фрагмента есть свой жизненный цикл, во многом совпадающий с жизненным циклом активности. Пожалуй, это единственное сходство с активностью.
Есть два варианта использования фрагментов в приложении (при желании можно использовать сразу оба варианта). Первый вариант заключается в том, что вы в разметке сразу указываете фрагмент с помощью тега fragment, так же как и с другими компонентами.
Поначалу фрагменты кажутся неудобными, так как количество кода увеличивается. Но если с ними работать постоянно, то станет понятнее их принцип.
Реакция разработчиков на появление фрагментов противоречива. Кто-то активно использует их в своих проектах, а кто-то их не переносит и использует альтернативные варианты. Похоже, в стане Гугла также идёт борьба между двумя группами программистов. Фрагменты постоянно развиваются, меняются и дорабатываются.
Первое знакомство
Для первого знакомства создадим стандартный проект на базе Empty Activity и вручную добавим фрагмент. Итак, после создания проекта выбираем из контекстного меню пакета New | Fragment | Fragment (Blank). В диалоговом окне мастера назначаем имя для фрагмента.
На этом этапе создание нового фрагмента напоминает создание новой активности. Мы создаём новый класс и автоматически генерируется макет для него fragment_cat.xml. Единственное отличие, в манифест ничего не добавляется.
Откроем макет фрагмента и немного отредактируем содержимое, добавив центрирование по центру. Также можно было добавить ImageView с изображением кота. Но не будем пока ничего усложнять.
В res/values/strings.xml содержится ресурс hello_blank_fragment. Изменим текст на что-то понятное:
Приготовления закончены. Осталось добавить фрагмент в активность. Открываем activity_main.xml и добавляем новый элемент.
В элементе fragment в атрибуте android:name указываем полное имя класса фрагмента.
Можно запустить проект и увидеть фрагмент внутри активности.
Жизненный цикл фрагмента
У фрагмента есть жизненный цикл, как у активности. Но число методов цикла гораздо больше. Откроем теперь файла класса фрагмента CatFragment и добавим новый код (выделено жирным).
Чтобы увидеть связь между жизненными циклами фрагмента и активности, добавим логи и в активность.
Снова запускаем проект и смотрим логи.
У фрагментов есть жизненный цикл, который во многом совпадает с жизненным циклом активности, внутри которой они находятся.
Два отдельных фрагмента
Повторим шаги создания нового фрагмента через готовый шаблон Fragment (Blank) и создадим два новых фрагмента KittyFragment и CounterFragment.
Разметку для KittyFragment копируем из старого примера.
Код также копируем с небольшим отличием. Если в активности мы размещали основной код в методе onCreate(), то для фрагмента используем onViewCreated(). Код шаблона для экономии я опущу.
Для счётчика котов (ворон больше считать не будем) напишем продвинутую версию, которая позволить не только увеличивать показания счётчика, но и уменьшать. В конце концов коты имеют привычку гулять сами по себе и пропадают из поля нашего зрения.
Разметка для фрагмента counter_fragment.xml.
Код для класса CounterFragment. Здесь также мы добавляем весь код в onViewCreated().
Осталось разместить два фрагмента в activity_main.xml.
Запускаем проект и видим два приложения в одном. В верхней части мы здороваемся с котёнком, а в нижней считаем котов. Обратите внимание, что весь код сосредоточился в классах фрагментов, а в MainActivity нам вообще ничего не пришлось писать. Фрагменты работают независимо и мы можем писать код в разных модулях.
Фрагменты
Существует два основных подхода в использовании фрагментов.
Первый способ основан на замещении родительского контейнера. Создаётся стандартная разметка и в том месте, где будут использоваться фрагменты, размещается контейнер, например, FrameLayout. В коде контейнер замещается фрагментом. При использовании подобного сценария в разметке не используется тег fragment, так как его нельзя менять динамически. Также вам придётся обновлять ActionBar, если он зависит от фрагмента. Здесь показан такой пример.
Второй подход является наиболее гибким и в целом предпочтительным способом использования фрагментов. Активность проверяет в каком режиме (свои размеры) он запущен и использует разную разметку из ресурсов. Графически это выглядит следующим образом.
Основные классы
Сами фрагменты наследуются от androidx.fragment.app.Fragment. Существует подклассы фрагментов: ListFragment, DialogFragment, PreferenceFragment, WebViewFragment и др. Не исключено, что число классов будет увеличиваться, например, появился ещё один класс MapFragment.
Как в любом офисе, спецманагер не делает работу своими руками, а использует помощников. Например, для транзакций (добавление, удаление, замена) используется класс-помощник android.app.FragmentTransaction.
Для сравнения приведу названия классов из библиотеки совместимости:
Как видите, разница в одном классе, который я привёл первым. Он используется вместо стандартного Activity, чтобы система поняла, что придётся работать с фрагментами. На данный момент студия создаёт проект на основе ActionBarActivity, который является подклассом FragmentActivity.
В одном приложении нельзя использовать новые фрагменты и фрагменты из библиотеки совместимости.
В 2018 году Гугл объявила фрагменты из пакета androd.app устаревшими. Заменяйте везде на версию из библиотеки совместимости. В 2020 году уже используют пакет androidx.fragment.app.
В версии Support Library 27.1.0 появились новые методы requireActivity() и requireContext(), которые пригодятся при написании кода, когда требуется наличие активности и нужно избежать ошибки на null.
Общий алгоритм работы с фрагментами будет следующим:
У каждого фрагмента должен быть свой класс. Класс наследуется от класса Fragment или схожих классов, о которых говорилось выше. Это похоже на создание новой активности или нового компонента.
Разметку для фрагмента можно создать программно или декларативно через XML.
Создание разметки для фрагмента ничем не отличается от создания разметки для активности. Вот отрывок кода из метода onCreateView():
Глядя на этот код, вы должные понять, что фрагмент использует разметку из файла res/layout/first_fragment.xml, которая содержит кнопку с идентификатором android:id=»@+id/button_first». Здесь также прослеживается сходство с подключением компонентов в активности. Обратите внимание, что перед методом findViewById() используется view, так как этот метод относится к компоненту, а не к активности, как мы обычно делали в программах, когда просто опускали имя активности. Т.е. в нашем случае мы ищем ссылку на кнопку не среди разметки активности, а внутри разметки самого фрагмента.
Нужно помнить, что в методе inflate() последний параметр должен иметь значение false в большинстве случаев.
FragmentManager
Класс FragmentManager имеет два метода, позволяющих найти фрагмент, который связан с активностью:
findFragmentById(int id) Находит фрагмент по идентификатору findFragmentByTag(String tag) Находит фрагмент по заданному тегу
Методы транзакции
Мы уже использовали некоторые методы класса FragmentTransaction. Познакомимся с ними поближе
add() Добавляет фрагмент к активности remove() Удаляет фрагмент из активности replace() Заменяет один фрагмент на другой hide() Прячет фрагмент (делает невидимым на экране) show() Выводит скрытый фрагмент на экран detach() (API 13) Отсоединяет фрагмент от графического интерфейса, но экземпляр класса сохраняется attach() (API 13) Присоединяет фрагмент, который был отсоединён методом detach()
Методы remove(), replace(), detach(), attach() не применимы к статичным фрагментам.
Перед началом транзакции нужно получить экземпляр FragmentTransaction через метод FragmentManager.beginTransaction(). Далее вызываются различные методы для управления фрагментами.
В конце любой транзакции, которая может состоять из цепочки вышеперечисленных методов, следует вызвать метод commit().
Аргументы фрагмента
Фрагменты должны сохранять свою модульность и не должны общаться друг с другом напрямую. Если один фрагмент хочет докопаться до другого, он должен сообщить об этом своему менеджеру активности, а он уже передаст просьбу другому фрагменту. И наоборот. Это сделано специально для того, чтобы было понятно, что менеджер тут главный и он не зря зарплату получает. Есть три основных способа общения фрагмента с активностью.
Фрагмент должен иметь только один пустой конструктор без аргументов. Но можно создать статический newInstance с аргументами через метод setArguments().
Доступ к аргументам можно получить в методе onCreate() фрагмента:
Динамически загружаем фрагмент в активность.
Вызываем метод в активности:
Если фрагмент должен сообщить о своих действиях активности, то следует реализовать интерфейс.
Управление стеком фрагментов
Фрагменты, как и активности, могут управляться кнопкой Back. Вы можете добавить несколько фрагментов, а потом через кнопку Back вернуться к первому фрагменту. Если в стеке не останется ни одного фрагмента, то следующее нажатие кнопки закроет активность.
Если вы вызовете метод addToBackStack() при удалении или замещении фрагмента, то будут вызваны методы фрагмента onPause(), onStop(), onDestroyView().
Когда пользователь нажимает на кнопку возврата, то вызываются методы фрагмента onCreateView(), onActivityCreated(), onStart() и onResume().
Рассмотрим пример реагирования на кнопку Back в фрагменте без использования стека. Активность имеет метод onBackPressed(), который реагирует на нажатие кнопки. Мы можем в этом методе сослаться на нужный фрагмент и вызвать метод фрагмента.
Теперь в классе фрагмента прописываем метод с нужным кодом.
Более желательным вариантом является использование интерфейсов. В некоторых примерах с фрагментами такой приём используется.
Интеграция Action Bar/Options Menu
Фрагменты могут добавлять свои элементы в панель действий или меню активности. Сначала вы должны вызвать метод Fragment.setHasOptionsMenu() в методе фрагмента onCreate(). Затем нужно задать настройки для методов фрагмента onCreateOptionsMenu() и onOptionsItemSelected(), а также при необходимости для методов onPrepareOptionsMenu(), onOptionsMenuClosed(), onDestroyOptionsMenu(). Работа методов фрагмента ничем не отличается от аналогичных методов для активности.
В активности, которая содержит фрагмент, данные методы автоматически сработают.
Если активность содержит собственные элементы панели действий или меню, то следует позаботиться, чтобы они не мешали вызовам методам фрагментов.
Код для активности:
Связь между фрагментом и активностью
Экземпляр фрагмента связан с активностью. Активность может вызывать методы фрагмента через ссылку на объект фрагмента. Доступ к фрагменту можно получить через методы findFragmentById() или findFragmentByTag().
Аргументы против использования фрагментов в Android
Недавно я выступал на конференции Droidcon в Париже с докладом (оригинал на французском), в котором рассматривал проблемы, возникшие у нас в Square при работе с фрагментами и возможности полного отказа от фрагментов.
В 2011-м мы решили использовать фрагменты по следующим причинам:
Чего ваши родители никогда не говорили вам о фрагментах
Жизненный цикл
Context в Android является божественным объектом, а Activity — это Context с дополнительным жизненным циклом. Божество с жизненным циклом? Ироничненько. Фрагменты в божественный пантеон не входят, но они с лихвой компенсируют этот недостаток очень сложным жизненным циклом.
Стив Помрой (Steve Pomeroy) сделал диаграмму всех переходов в жизненном цикле фрагмента, и особого оптимизма она не внушает:
Сделано Стивом Помроем, слегка изменено с целью удалить жизненный цикл Activity и выложено под лицензией CC BY-SA 4.0.
Жизненный цикл ставит перед вами множество интереснейших вопросов. Что можно, а что нельзя делать в каждой упомянутой выше функции обратного вызова? Они вызываются синхронно, или по очереди? Если по очереди, то в каком порядке?
Отладка затрудняется
С этим кодом довольно сложно разобраться, что затрудняет процесс поиска ошибок в вашем приложении:
Если вы хоть раз обнаруживали, что у вас на руках созданный заново после поворота экрана и не присоединённый к Activity фрагмент, то вы понимаете о чём я говорю (и ради всего святого, не испытывайте судьбу и не упоминайте при мне про вложенные фрагменты).
Закон обязывает меня (по крайней мере я читал об этом на Coding Horror) приложить к посту следующее изображение, так что не обессудьте:
Магия создания фрагментов
Что мы поняли, поработав со фрагментами
Несмотря на все проблемы фрагментов, из работы с ними можно вынести бесценные уроки, которые мы будем применять при создании своих приложений:
Адаптивный интерфейс: фрагменты против представлений
Фрагменты
Давайте посмотрим на простой пример с фрагментами: интерфейс, состоящий из списка и детализированного представления каждого элемента списка.
HeadlinesFragment представляет из себя список элементов:
Переходим к более интересному: ListFragmentActivity должна разбираться с тем, показывать ли детали на том же экране что и список, или нет:
Представления
Давайте перепишем этот код, используя самописные представления. Во-первых, мы введём понятие контейнера ( Container ), который может показать элемент списка, а также обрабатывает нажатия назад:
Activity знает, что у неё всегда есть на руках контейнер, и просто делегирует ему нужную работу:
Реализация списка тоже является довольно тривиальной:
Переходим к интересному: загрузке разных разметок в зависимости от классификаторов ресурсов:
Реализация этих контейнеров:
В дальнейшем эти контейнеры можно будет абстрагировать и построить всё приложение на подобных принципах. В результате нам не только не нужны будут фрагменты, но и код получится более доступным для восприятия.
Представления и презентеры
Самописные представления — это хорошо, но хочется большего: хочется выделить бизнес-логику в отдельные контроллеры. Мы будем называть подобные контроллеры презентерами ( Presenters ). Введение презентеров позволит нам сделать код более читабельным и упростит дальнейшее тестирование:
Давайте посмотрим на код, взятый с экрана редактирования скидок приложения Square Register.
Презентер осуществляет высокоуровневую манипуляцию представлением:
Тестировать этот презентер очень просто:
Управление backstack’ом
Мы написали библиотеку Flow, чтобы упростить себе работу с backstack’ом, а Ray Rayan написал о ней очень хорошую статью. Не вдаваясь особо в подробности, скажу, что код получился довольно простым, так как асинхронные транзакции больше не нужны.
Я глубоко завяз в спагетти из фрагментов, что мне делать?
Вынесите из фрагментов всё, что можно. Код, относящийся к интерфейсу, должен уйти в ваши собственные представления, а бизнес-логику нужно убрать в презентеры, которые знают, как работать с вашими представлениями. После этого у вас останется почти пустой фрагмент, создающий ваши собственные представления (а те, в свою очередь, знают как и с какими презентерами себя связать):
Всё, фрагмент можно удалить.
Мигрирование с фрагментов было не простым, но мы прошли его — благодаря отличной работе Dimitris Koutsogiorgas и Ray Ryan.
А для чего нужны Dagger и Mortar?
Обе эти библиотеки перпендикулярны фрагментами: их можно использовать как с фрагментами, так и без оных.
Dagger позволяет вам спроектировать ваше приложение в виде графа несвязных компонентов. Dagger берёт на себя работу по связыванию компонентов друг с другом, упрощая таким образом извлечение зависимостей и написание классов с единственной обязанностью.
Mortar работает поверх Dagger’а, и у него есть два важных преимущества:
Заключение
Мы интенсивно использовали фрагменты, но со временем передумали и избавились от них:
Немного о Fragment
Добрый день Хабр, в этой статье я хочу рассказать о таком интересном элементе как Fragment, эта статья не научный прорыв, а просто небольшой туториал о использовании этого элемента. Всем, кому интересно узнать, что-то новое, прошу под кат.
Fragment — модульная часть activity, у которой свой жизненный цикл и свои обработчики различных событий. Android добавил фрагменты с API 11, для того, чтобы разработчики могли разрабатывать более гибкие пользовательские интерфейсы на больших экранах, таких как экраны планшетов. Через некоторое время была написана библиотека, которая добавляет поддержку фрагментов в более старые версии.
Основные классы
Есть три основных класса:
android.app.Fragment — от него, собственно говоря. и будут наследоваться наши фрагменты
android.app.FragmentManager — с помощью экземпляра этого класса происходит все взаимодействие между фрагментами
android.app.FragmentTransaction — ну и этот класс, как понятно по названию, нужен для совершения транзакций.
В настоящее время появляются разновидности класса Fragment, для решения определенных задач — ListFragment, PreferenceFragment и др.
Основы работы с fragment’ами
Чтобы создать фрагмент все что нужно это наследовать свой класс от Fragment. Чтобы привязать фрагмент к определенной разметке нужно определить в нем метод onCreateView(). Этот метод возвращает View, которому и принадлежит ваш фрагмент.
Чтобы получить это View из любого места фрагмента достаточно вызвать getView()
Фрагмент мы создали, но хотелось бы поместить его на экран. Для этого нам нужно получить экземпляр FragmentManager и совершить нужную нам транзакцию.
Сначала нужно узнать что мы с фрагментом можем сделать:
add() — добавление фрагмента
remove() — удаление фрагмента
replace() — замена фрагмента
hide() — делает фрагмент невидимым
show() — отображает фрагмент
Так же для того чтобы добавлять наши транзакции в стек, как это происходит по умолчанию с активностями, можно использовать addToBackStack(String), а чтобы вернуть предыдущее состояние стэка нужно вызвать метод popBackStack().
Добавим фрагмент на экран:
Как связать activity и fragment?
Чтобы вызывать методы активити, достаточно получить его экземпляр через метод getActivity()
Для того же чтобы получить доступ к фрагменту, у нас есть ссылка на объект фрагмента, которую мы создали при совершении транзакции
Если нам нужно обрабатывать события фрагмента из активити, то самое лучшее решение это у активити реализовать интерфейс и во фрагменте пытаться привести родительское активити к обьекту этого интерфейса.
Покажу как это делается для второго варианта, так как для android более правильно использовать Parcel для передачи параметров между активностями и фрагментами.
Здесь мы реализовали интерфейс Parcelable в классе, который хотим передавать между фрагментами.
Чтобы передать его во фрагмент нужно сделать следующее:
Дальше нужно всего лишь получить переданный нами объект в методе onCreateView() нового фрагмента:
UPD. исправил получение объекта obj из getArguments(), спасибо firexel
Анимация фрагментов
Мы научились создавать фрагменты, совершать действия над ними, взаимодействовать с активити и из активити, но чтобы все это выглядело представительно, фрагменты можно немного оживить, добавив к ним анимации.
Чтобы создать свою анимацию добавления и удаления фрагмента? нужно создать два файла в директории res/animator, один из них будет служить для анимации добавления, второй для удаления
Приведу пример одного из них:
Корневым элементом служит objectAnimator, его атрибуты и задают параметры анимации.
Теперь нам нужно вызвать метод setCustomAnimations() с нашими анимациями и при следующей транзакции наши фрагменты оживут.
Много еще можно говорить о фрагментах, хотя большая часть всего уже написана, я же хотел объединить все основные знания о фрагментах, чтобы человек, ничего не знающий о этом элементе, после прочтения стал свободно ими пользоваться.
Красота из фрагментов. Как улучшить UI в Android c помощью класса Fragment
Содержание статьи
Грабли Activity
Большинство туториалов, демонстрирующих фишки Android-разработки, начинаются одинаково: неопытным разработчикам предлагают накидать все визуальные элементы прямо в XML-разметку главного Activity. Выглядит это примерно так:
Такая проектировка входит в привычку, и проект заполняется новыми Activity со все более сложной разметкой. Как итог — даже минимально полезное приложение обрастает стеком Activity, сжирающим всю оперативную память, а в разработчика летят камни и двойки в Google Play.
Сегодня я хочу поделиться с тобой широко известным «секретом»: Activity совсем не предназначены для массового использования. Наоборот, в приложении это штучный инструмент, который идет в ход только в крайних случаях. Повсеместная генерация новых Activity создает серьезные проблемы, которые делают работу приложения непредсказуемой. И даже если на твоем устройстве все стабильно, в мире немыслимое количество Android-устройств, на большинстве из которых твое приложение будет падать.
Используя дополнительные Activity для вывода меню и других мелочей, ты подвергаешь опасности все логические связи внутри приложения. Activity собираются в стек, и ОС может начать выгружать их по одному или группой. Когда пользователь захочет вернуться в предыдущее окно, Activity может уже быть уничтожен, и юзер выпадет из приложения.
Кроме проблем с сохранением логики, есть и рутина поддержки ООП-кода: плотно завязанные на Activity интерфейсы практически невозможно развивать дальше. Масштабирование, быструю замену одних элементов на другие, анимацию — все это будет очень тяжело реализовать в новых версиях приложения.
Благодаря моде на единую экосистему все мобильные приложения стали в целом очень похожи. Стоит только нащупать тропинку да немного потренироваться, и тогда качество начинает быстро расти. Следуя принципу «критикуя — предлагай», мы сейчас напишем приложение, реализующее универсальный пользовательский интерфейс. Это будет интересно не только с академической точки зрения — уверен, написанный сегодня код ты сможешь легко встроить в свои проекты. Итак, начнем!
Fragments
Чтобы работать с UI было проще и быстрее, Google создала фрагмент (Fragment) — класс — прослойку между Activity и визуальными составляющими программы. С одной стороны, это контейнер для любых View-объектов, которые могут быть показаны пользователю. С другой — продолжение Activity, от которого Fragment получает всю информацию об изменениях в жизненном цикле.
Рис. 1. Жизненный цикл фрагмента (с) Google
Xakep #215. Второй фактор
FragmentTransaction
Зная, что работа с фрагментами будет насыщенной, Google заблаговременно создала для этого специальные инструменты. Классы FragmentManager и FragmentTransaction аккумулируют в себе все процессы: создание новых фрагментов и их удаление, передачу данных и так далее.
Хочу заметить, что классы, работающие с фрагментами, доступны в двух вариантах. Рекомендую использовать более новую версию — это те, у которых в пути импорта присутствует строчка android.support.v4. Это большая библиотека, созданная для организации обратной совместимости. Компания Google бережно относится к устройствам на всех версиях ОС, а библиотеки позволяют использовать новшества разработки даже при работе со старым API.
FrameLayout
Добавить новый фрагмент в FrameLayout можно по-разному: в FragmentTransaction доступны схожие по функциональности методы замены (replace) и добавления (add).
Несмотря на внешнюю схожесть, нужно хорошо понимать, какого результата ты можешь добиться. Метод replace работает очень просто — добавит фрагмент и, если в контейнере раньше что-то было, удалит старые фрагменты.
Метод add же создает стек из фрагментов, и каждый новый вызов помещает на верх стека новый экземпляр. Важно, что при появлении нового фрагмента старые объекты в стеке не получают никаких оповещений и работают так, как будто бы ничего не произошло. Это значит, что метод onPause для них не выполнится и они продолжат расходовать ресурсы устройства, хотя пользователь перестанет их видеть.
Такая растрата выделенной памяти бывает оправданна, например если хочется организовать предобработку данных: фрагмент может что-нибудь подгрузить, пока будет незаметен, а потом показать уже готовый результат.
Действий с фрагментами может быть несколько, они будут аккумулироваться до тех пор, пока не выполнится метод commit.
Рис. 2. Сменяемые фрагменты
Смена фрагментов
Довольно часто нужно понять, какой именно фрагмент сейчас был показан пользователю. Самый очевидный способ — завести в Activity переменную, запоминающую номер фрагмента, выведенного на экран. Но с таким подходом фрагменты становятся жестко привязаны к экземпляру Activity, и, как только он будет уничтожен ОС, счетчик фрагментов будет утерян.
Но этот подход работает ровно до тех пор, пока в приложении нет отдельного дизайна для портретной и альбомной ориентаций экрана: новый XML-файл подгрузится только вместе с созданием нового экземпляра Activity, в методе onCreate. Конечно, можно придумать костыли и на этот случай, но есть вариант проще.
Фрагментам можно назначать уникальные теги, по которым возможно вычислить тот, что сейчас видит пользователь. Теги задаются в тех же методах replace и add дополнительным параметром.
Это удобно, поскольку в полноценном UI будет несколько фрагментов, и в зависимости от ситуации их нужно будет чередовать на экране.
Portrait & Landscape
Раз уж всплыла тема особого UI для альбомной ориентации, посмотрим, как это реализовать с помощью фрагментов. Напомню, что файлы XML-разметки для разных типов экрана должны иметь одинаковое имя, но располагаться в папках с особыми именами. В частности, альбомную верстку нужно поместить в папку landscape-land.
Поскольку пространства стало больше, можно поместить рядом сразу несколько фрагментов. Это будет еще один, третий фрагмент, явно указанный в XML-коде.
Он появится только при повороте экрана, но будет работать точно так же, как и любой другой, загруженный через контейнер FrameLayout.
Рис. 3. Дополняем UI третьим фрагментом
Анимация
В мире Material Design важное место занимают плавные переходы между элементами UI. Если построить дизайн на основе фрагментов, организовать анимацию будет просто.
В данном случае появление фрагмента будет сопровождаться эффектом постепенного проявления (fade_in). Если на экране будет какой-то другой фрагмент, то он так же плавно исчезнет (fade_out).
В Android уже есть заготовленные сценарии анимации, которых тебе наверняка хватит. Но можно создать и свой вариант, описав движения в привычном формате XML. Выглядеть это будет примерно так:
«Оживлять» фрагменты намного проще, чем целые Activity. Запуск нового окна — трудоемкий процесс, который не так легко анимировать. К примеру, для новых экранов fade-эффекты практически не используются, так как они будут очень плохо выглядеть на слабых устройствах. А меняя один фрагмент другим, ты перерисовываешь только часть экрана, и, значит, ресурсов тратится меньше.
RetainedFragment
Ты уже знаешь, что многопоточная разработка требует повышенного внимания, иначе можно потерять все вычисления и осложнить жизнь сборщику мусора. Мы уже раньше разбирали, насколько опасным может быть пересоздание Activity, в случае с фрагментами ситуация та же самая: нужно сохранять связь между новыми компонентами и дополнительными потоками.
Для фрагментов сохранять связи легче — и это еще один повод отказаться от Activity. Достаточно только при создании фрагмента вызвать метод setRetainInstance.
Теперь при повороте экрана ОС «сохранит» фрагмент, и все дополнительные потоки вернут результат именно в тот фрагмент, который видит пользователь. Кстати, благодаря реактивному программированию код стал гораздо проще.
Но вообще, это не совсем сохранение: в оригинале это называется Retain (помнить, удерживать), и смысл происходящего отличается от привычного Save.
Хотя RetainedFragment и сохранит идентификаторы того фрагмента, который был до поворота экрана, его содержимое, скорее всего, будет потеряно. Данные, отображаемые во View-элементах, будут утрачены, поэтому о них нужно позаботиться самостоятельно.
Конечно, в некоторых случаях все не так печально. К объектам из базовых классов можно добавить специальный параметр:
Тогда, к примеру, EditText и TextView сохранят свои данные самостоятельно. Но это точно не сработает с самописными View-объектами, а может не сработать и с базовыми. К счастью, у нас есть печеньки и сериализация!
Bundle
Уверен, ты уже не раз встречал этот класс как удобный способ сериализации (сохранения в мире ООП) данных. Чтобы было что восстанавливать, нужно данные сначала сохранить — для этого в фрагментах доступен специальный метод, вызываемый перед выгрузкой фрагмента из памяти.
Естественно, фрагменты можно снабжать и стартовым набором параметров, которые потом будут доступны точно таким же способом.
Взаимодействие
Приложение должно уметь перекидывать данные из компонента в компонент, и с этим у фрагментов все хорошо. Передавать данные между Activity и фрагментом возможно в любой момент времени, в том числе и во время их активной работы. Делается это по всем канонам ООП — сначала получить ссылку на объект, а затем спокойно вызывать его методы.
Этот метод вызывается ОС при создании фрагмента и позволяет получить контекст приложения. Контекст дает возможность не только пользоваться всеми ресурсами приложения, но и добраться до сгенерировавшего вызов Activity. Получая доступ к контексту или Activity, нужно быть очень внимательным и удалять ссылки, когда эти объекты больше не нужны: если есть хотя бы одна ссылка, сборщик мусора оставит объект в памяти.
Опять же, в целях экономии Google просит разработчиков не связывать фрагменты между собой напрямую, а использовать Activity в качестве посредника. В противном случае велик риск запутаться в уничтоженных и еще живых элементах UI и перерасходовать выделенную устройством память.
Outro
Уверен, после этой статьи ты станешь с большим пиететом относиться к базовым компонентам ОС и реже их использовать. Фрагменты позволяют создавать удобный и гибкий UI, так что дизайнеры будут рады, если те станут основой интерфейсов. А в качестве примера для подражания всегда помни про приложение Google Play — во время его работы генерируется всего один Activity!
Обязательно скачай с нашего сайта исходники разобранного примера — полноценный листинг читается и запоминается легче. Если остались какие-то вопросы или предложения с замечаниями :), пиши мне. Удачи!