Android жизненный цикл фрагмента. Фрагменты. Жизненный цикл Fragment. RadioButton. RadioGroup. Продолжение доступно на платных тарифах


Последнее обновление: 30.10.2015

Каждый класс фрагмента наследуется от базового класса Fragment и имеет свой жизненный цикл, состоящий из ряда этапов:

    onAttach() : при выполнении этого метода фрагмент ассоциируется с определенной activity. На этом этапе фрагмент и activity еще не полностью инициализированы.

    onCreate() : происходит создание фрагмента. Этот метод вызывается после вызова соответствующего метода onCreate() у activity.

    onCreateView() : фрагмент создает визуальный интерфейс

    onActivityCreated() : вызывается после создания activity. С этого момента к компонентам интерфейса можно обращаться через метод findViewById()

    onStart() : вызывается, когда фрагмент становится видимым

    onResume() : фрагмент становится активным

    onPause() : фрагмент продолжает оставаться видимым, но уже не активен

    onStop() : фрагмент больше не является видимым

    onDestroyView() : уничтожается интерфейс, представляющий фрагмент

    onDestroy() : окончательно уничтожение фрагмента

В коде класса фрагмента мы можем переопределить все или часть из этих методов.

Поскольку фрагменты часто используются для определенных целей, например, для вывода списка каких-нибудь объектов, то по умолчанию нам доступны классы, производные от Fragment, уже обладающие определенными возможностями:

    ListFragment : управляет списком элементов с помощью одного из адаптеров

    DialogFragment : используется для создания диалоговых окон

    PreferenceFragment : используется для управления настройками приложения

Для создания нового фрагмента можно использовать обычные классы. Однако среда Android Studio уже предлагает ряд встроенных шаблонов:

При создании будет предложено установить имя проекта, название файла разметки интерфейса и еще ряд опций:

Генерируемый Android Studio класс фрагмента будет во многом аналогичен ранее использовавшимся:

Package com.example.eugene.testapp; import android.app.Activity; import android.net.Uri; import android.os.Bundle; import android.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class BlankFragment extends Fragment { private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; private String mParam1; private String mParam2; private OnFragmentInteractionListener mListener; // Фабрика для создания фрагмента public static BlankFragment newInstance(String param1, String param2) { BlankFragment fragment = new BlankFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM1, param1); args.putString(ARG_PARAM2, param2); fragment.setArguments(args); return fragment; } public BlankFragment() { // Конструктор } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mParam1 = getArguments().getString(ARG_PARAM1); mParam2 = getArguments().getString(ARG_PARAM2); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_blank, container, false); } public void onButtonPressed(Uri uri) { if (mListener != null) { mListener.onFragmentInteraction(uri); } } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnFragmentInteractionListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener"); } } @Override public void onDetach() { super.onDetach(); mListener = null; } public interface OnFragmentInteractionListener { public void onFragmentInteraction(Uri uri); } }

Поэтому мы можем создавать фрагменты вручную, либо использовать один из шаблонов, предоставляемый Android Studio.

Создать хороший UI сложно, особенно если у тебя еще не так много опыта в этой области. Поэтому вот тебе быстрый тест на знание вопроса: если ты привык, что для нового окна обязательно нужен Activity или вместо плавной анимации в свеженаписанной программе почему-то происходят конвульсии, - эта статья для тебя 🙂

Грабли Activity

Большинство туториалов, демонстрирующих фишки Android-разработки, начинаются одинаково: неопытным разработчикам предлагают накидать все визуальные элементы прямо в XML-разметку главного Activity. Выглядит это примерно так:

Public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ...

Такая проектировка входит в привычку, и проект заполняется новыми Activity со все более сложной разметкой. Как итог - даже минимально полезное приложение обрастает стеком Activity, сжирающим всю оперативную память, а в разработчика летят камни и двойки в Google Play.

А все потому, что ОС Android совершенно не обещает держать твои Activity живыми. Как ты помнишь, эти компоненты существуют независимо друг от друга, обладая особым жизненным циклом. Если Activity переходит в состояние onPause , а происходит это довольно часто, он становится котейкой Шредингера: нельзя заранее знать, будет он жив или нет.

Используя дополнительные Activity для вывода меню и других мелочей, ты подвергаешь опасности все логические связи внутри приложения. Activity собираются в стек, и ОС может начать выгружать их по одному или группой. Когда пользователь захочет вернуться в предыдущее окно, Activity может уже быть уничтожен, и юзер выпадет из приложения.

Кроме проблем с сохранением логики, есть и рутина поддержки ООП-кода: плотно завязанные на Activity интерфейсы практически невозможно развивать дальше. Масштабирование, быструю замену одних элементов на другие, анимацию - все это будет очень тяжело реализовать в новых версиях приложения.

Благодаря моде на единую экосистему все мобильные приложения стали в целом очень похожи. Стоит только нащупать тропинку да немного потренироваться, и тогда качество начинает быстро расти. Следуя принципу «критикуя - предлагай», мы сейчас напишем приложение, реализующее универсальный пользовательский интерфейс. Это будет интересно не только с академической точки зрения - уверен, написанный сегодня код ты сможешь легко встроить в свои проекты. Итак, начнем!

Fragments

Чтобы работать с UI было проще и быстрее, Google создала фрагмент (Fragment) - класс - прослойку между Activity и визуальными составляющими программы. С одной стороны, это контейнер для любых View-объектов, которые могут быть показаны пользователю. С другой - продолжение Activity, от которого Fragment получает всю информацию об изменениях в жизненном цикле.

У фрагментов, как и у Activity, есть свой (правда, более оригинальный) жизненный цикл. К примеру, работать с UI сразу после создания фрагмента невозможно, нужно ждать загрузки всех элементов - после метода onCreate выполнится метод onCreateView , где и можно будет загрузить элементы.

Public class FragmentOne extends Fragment { ... public View onCreateView(...) { View view =inflater.inflate(R.layout.fragment_one, container,false); TextView textView = (TextView)view.findViewById(R.id.fo_text); textView.setText("Hello, i"m first fragment!"); return view; ...

В фрагменте тоже можно переопределять любые методы, отслеживающие состояние окна. Так, если приложение уйдет в фон, в Activity выполнится onPause , а затем метод с точно таким же названием выполнится здесь. Это может быть полезно - удобно для отключения от сторонних объектов, например привязанных сервисов (bound service).

FragmentTransaction

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

Объект FragmentManager создавать не нужно, он уже есть в каждом Activity, нужно только получить на него ссылку. А все действия будут проходить через FragmentTransaction , который затем самостоятельно передаст данные менеджеру фрагментов.

FragmentManager manager = getSupportFragmentManager(); FragmentTransaction transaction = manager.beginTransaction();

Хочу заметить, что классы, работающие с фрагментами, доступны в двух вариантах. Рекомендую использовать более новую версию - это те, у которых в пути импорта присутствует строчка android.support.v4. Это большая библиотека, созданная для организации обратной совместимости. Компания Google бережно относится к устройствам на всех версиях ОС, а библиотеки позволяют использовать новшества разработки даже при работе со старым API.

Import android.support.v4.app.FragmentTransaction; import android.support.v4.app.Fragment;...

FrameLayout

Часто данные в UI приходят динамически, в зависимости от происходящих событий. Чтобы было куда поместить картинку или текст, существует специальный контейнер - FrameLayout . В этом и есть его основная задача - зарезервировать на экране место для любого объекта класса View, который можно будет подгрузить позднее. Фрагменты тоже живут в таких контейнерах.

Добавить новый фрагмент в FrameLayout можно по-разному: в FragmentTransaction доступны схожие по функциональности методы замены (replace) и добавления (add).

Transaction.replace(R.id.frame_container, fragmentObject);

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

Transaction.add(R.id.frame_container, fragment);

Метод add же создает стек из фрагментов, и каждый новый вызов помещает на верх стека новый экземпляр. Важно, что при появлении нового фрагмента старые объекты в стеке не получают никаких оповещений и работают так, как будто бы ничего не произошло. Это значит, что метод onPause для них не выполнится и они продолжат расходовать ресурсы устройства, хотя пользователь перестанет их видеть.

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

Действий с фрагментами может быть несколько, они будут аккумулироваться до тех пор, пока не выполнится метод commit.

Transaction.commit();

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «сайт», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score!

Рисунок 2. Жизненный цикл фрагмента (во время выполнения операции)

Для создания фрагмента необходимо создать подкласс класса (или его существующего подкласса). Класс имеет код, во многом схожий с кодом . Он содержит методы обратного вызова, аналогичные методам операции, такие как , и . На практике, если требуется преобразовать существующее приложение Android так, чтобы в нем использовались фрагменты, достаточно просто переместить код из методов обратного вызова операции в соответствующие методы обратного вызова фрагмента.

Как правило, необходимо реализовать следующие методы жизненного цикла:

Система вызывает этот метод, когда создает фрагмент. В своей реализации разработчик должен инициализировать ключевые компоненты фрагмента, которые требуется сохранить, когда фрагмент находится в состоянии паузы или возобновлен после остановки. Система вызывает этот метод при первом отображении пользовательского интерфейса фрагмента на дисплее. Для прорисовки пользовательского интерфейса фрагмента следует возвратить из этого метода объект , который является корневым в макете фрагмента. Если фрагмент не имеет пользовательского интерфейса, можно возвратить null. Система вызывает этот метод как первое указание того, что пользователь покидает фрагмент (это не всегда означает уничтожение фрагмента). Обычно именно в этот момент необходимо фиксировать все изменения, которые должны быть сохранены за рамками текущего сеанса работы пользователя (поскольку пользователь может не вернуться назад).

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

Отображение перемещаемого диалогового окна. Использование этого класса для создания диалогового окна является хорошей альтернативой вспомогательным методам диалогового окна в классе . Дело в том, что он дает возможность вставить диалоговое окно фрагмента в управляемый операцией стек переходов назад для фрагментов, что позволяет пользователю вернуться к закрытому фрагменту. Отображение списка элементов, управляемых адаптером (например, ), аналогично классу . Этот класс предоставляет несколько методов для управления списком представлений, например, метод обратного вызова для обработки нажатий. Отображение иерархии объектов в виде списка, аналогично классу . Этот класс полезен, когда в приложении создается операция «Настройки».

Добавление пользовательского интерфейса

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

Чтобы создать макет для фрагмента, разработчик должен реализовать метод обратного вызова , который система Android вызывает, когда для фрагмента наступает время отобразить свой макет. Реализация этого метода должна возвращать объект , который является корневым в макете фрагмента.

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

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

Например, код подкласса класса , загружающий макет из файла example_fragment.xml , может выглядеть так:

Public static class ExampleFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false); } }

Создание макета

В приведенном коде конструкция R.layout.example_fragment является ссылкой на ресурс макета по имени example_fragment.xml , хранящийся в ресурсах приложения. Подробные сведения о создании макета в XML см. в статье .

Пример операции, использующей фрагмент в качестве фонового потока, без пользовательского интерфейса, приведен в образце кода FragmentRetainInstance.java , входящем в число образцов в SDK (и доступном при помощи Android SDK Manager). Путь к нему в системе - /APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java .

Управление фрагментами

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

Ниже указаны действия, которые позволяет выполнить :

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

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

Как было показано в предыдущем разделе, можно использовать класс для открытия , что позволяет выполнять транзакции с фрагментами, например, добавление и удаление.

Выполнение транзакций с фрагментами

Большим достоинством использования фрагментов в операции является возможность добавлять, удалять, заменять их и выполнять другие действия с ними в ответ на действия пользователя. Любой набор изменений, вносимых в операцию, называется транзакцией. Ее можно выполнить при помощи API-интерфейсов в . Каждую транзакцию можно сохранить в стеке переходов назад, которым управляет операция. Это позволит пользователю перемещаться назад по изменениям во фрагментах (аналогично перемещению назад по операциям).

Взаимодействие с операцией

public static class FragmentA extends ListFragment { OnArticleSelectedListener mListener; ... @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnArticleSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); } } ... }

Если операция не реализовала интерфейс, фрагмент генерирует исключение . В случае успеха элемент mListener будет содержать ссылку на реализацию интерфейса OnArticleSelectedListener в операции, чтобы фрагмент A мог использовать события совместно с операцией, вызывая методы, определенные интерфейсом OnArticleSelectedListener . Например, если фрагмент A является расширением класса , то всякий раз, когда пользователь нажимает элемент списка, система вызывает во фрагменте. Этот метод, в свою очередь, вызывает метод onArticleSelected() , чтобы использовать событие совместно с операцией:

Public static class FragmentA extends ListFragment { OnArticleSelectedListener mListener; ... @Override public void onListItemClick(ListView l, View v, int position, long id) { // Append the clicked item"s row ID with the content provider Uri Uri noteUri = ContentUris. (ArticleColumns.CONTENT_URI, id); // Send the event and Uri to the host activity mListener.onArticleSelected(noteUri); } ... }

Параметр id , передаваемый методу , - это идентификатор строки с выбранным элементом списка, который операция (или другой фрагмент) использует для получения статьи от объекта приложения.

Дополнительные сведения о работе с поставщиком контента приводятся в документе .

Добавление элементов в строку действий

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

Любые пункты, добавляемые фрагментом в Меню вариантов, присоединяются к уже существующим. Кроме того, фрагмент принимает обратные вызовы метода , когда пользователь выбирает пункт меню.

Разработчик может также зарегистрировать представление в макете своего фрагмента, чтобы предоставить контекстное меню. Для этого следует вызвать метод . Когда пользователь открывает контекстное меню, фрагмент принимает вызов метода . Когда пользователь выбирает пункт меню, фрагмент принимает вызов метода .

Примечание. Хотя фрагмент принимает обратный вызов по событию «выбран пункт меню» для каждого добавленного им пункта, операция первой принимает соответствующий обратный вызов, когда пользователь выбирает пункт меню. Если имеющаяся в операции реализация обратного вызова по событию «выбран пункт меню» не обрабатывает выбранный пункт, событие передается методу обратного вызова во фрагменте. Это справедливо для Меню вариантов и контекстных меню.

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

Управление жизненным циклом фрагмента

Рисунок 3 . Влияние жизненного цикла операции на жизненный цикл фрагмента

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

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

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

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

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

Внимание! Если возникнет необходимость в объекте внутри объекта класса , можно вызвать метод . Однако разработчик должен быть внимательным и вызывать метод только когда фрагмент прикреплен к операции. Если фрагмент еще не прикреплен или был откреплен в конце его жизненного цикла, метод возвратит null.

Согласование с жизненным циклом операции

Жизненый цикл операции, содержащей фрагмент, непосредственным образом влияет на жизненый цикл фрагмента, так что каждый обратный вызов жизненного цикла операции приводит к аналогичному обратного вызову для каждого фрагмента. Например, когда операция принимает вызов , каждый ее фрагмент принимает .

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

OnCreate() , фрагмент внутри этой операции принимает всего лишь метод обратного вызова .

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

Однако, когда операция выходит из этого состояния, продвижение фрагмента по его жизненному циклу снова осуществляется операцией.

Пример:

Чтобы суммировать все сказанное в этом документе, рассмотрим пример операции, использующей два фрагмента для создания макета с двумя панелями. Операция, код которой приведен ниже, включает в себя один фрагмент для отображения списка пьес Шекспира, а другой - для отображения краткого содержания пьесы, выбранной из списка. В примере показано, как следует организовывать различные конфигурации фрагментов в зависимости от конфигурации экрана.

Примечание. Полный исходный код этой операции находится в разделе .

Главная операция применяет макет обычным способом, в методе :

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_layout); }

Здесь применяется макет fragment_layout.xml:

Пользуясь этим макетом, система создает экземпляр класса TitlesFragment (список пьес), как только операция загрузит макет. При этом объект (в котором будет находиться фрагмент с кратким содержанием) занимает место в правой части экрана, но поначалу остается пустым. Как будет показано ниже, фрагмент не помещается в , пока пользователь не выберет элемент в списке.

Однако не все экраны достаточно широки, чтобы отображать краткое содержание рядом со списком пьес. Поэтому описанный выше макет используется только при альбомной ориентации экрана и хранится в файле res/layout-land/fragment_layout.xml .

Когда же устройство находится в книжной ориентации, система применяет макет, приведенный ниже, который хранится в файле res/layout/fragment_layout.xml:

В этом макете присутствует только объект TitlesFragment . Это означает, что при книжной ориентации устройства виден только список пьес. Когда пользователь нажимает на элемент списка в этой конфигурации, приложение запускает новую операцию для отображения краткого содержания, а не загружает второй фрагмент.

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

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

Второй фрагмент, DetailsFragment , отображает краткое содержание пьесы, выбранной в списке TitlesFragment:

Вспомним код класса TitlesFragment: если пользователь нажимает на пункт списка, а текущий макет не включает в себя представление R.id.details (которому принадлежит фрагмент DetailsFragment), то приложение запускает операцию DetailsActivity для отображения содержимого элемента.

Public static class DetailsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { // If the screen is now in landscape mode, we can show the // dialog in-line with the list so we don"t need this activity. finish(); return; } if (savedInstanceState == null) { // During initial setup, plug in the details fragment. DetailsFragment details = new DetailsFragment(); details.setArguments(getIntent().getExtras()); getFragmentManager().beginTransaction().add(android.R.id.content, details).commit(); } } }

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

Дополнительные образцы кода, использующего фрагменты (и файлы с полным исходным кодом этого примера), доступны в приложении-примере API Demos в разделе (которое можно загрузить из ).

Рассказывая о разработке под Android невозможно не упомянуть фрагменты .

Что такое Fragment?

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

Фрагмент содержит внутри себя элементы интерфейса, точно так же, как и Activity, однако, между этими двумя понятиями есть несколько ключевых отличий:

  • Фрагмент содержится внутри Activity .
  • Внутри Activity может находиться несколько фрагментов , то есть на экране может быть несколько фрагментов сразу, тогда как Activity всегда одна в каждый момент времени.

Реализация приложения без фрагментов

Давайте посмотрим на конкретный пример, чтобы лучше понять, как это работает.

Представим, что у нас есть приложение, содержащее два экрана:

  • Экран с лентой новостей.
  • Экран с деталями о новости, открывается по клику на элемент списка в предыдущей Activity .

Вот так оно выглядит на телефоне:


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

А теперь представьте, что нам также нужно сделать приложение для планшета:


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

Будет ли такой подход работать? Да. Корректен ли он? Абсолютно нет.

При использовании подобного подхода мы мало того, что создаём дополнительные ненужные Activity , так ещё и лишены возможности переиспользовать код - почти весь код в TabletFeedActivity будет просто скопипащен из FeedActivity и DetailActivity !

И тут нам на помощь приходят фрагменты!

Реализация приложения с фрагментами

Мы оставляем FeedActivity и DetailActivity , но вводим дополнительно два класса - FeedFragment (Fragment #1 на картинке ниже) и DetailFragment (Fragment #2 на картинке ниже).

В случае использования телефона FeedFragment располагается в FeedActivity , а DetailFragment - в DetailActivity:

Если же приложение запущено на планшете, мы добавляем оба фрагмента в FeedActivity , DetailActivity при этом не используется вообще. Всё!


Таким образом мы не пишем лишнего кода и наше приложение становится полностью адаптивным.

Также как и у Activity , у фрагмента есть жизненный цикл.

У Fragment может быть три состояния:

  • Остановлен - Fragment не виден на экране. Он существует, но недоступен для пользовательского взаимодействия и может быть уничтожен, если связанная с ним Activity будет уничтожена.
  • Приостановлен - Fragment виден на экране, но может быть перекрыт другими элементами интерфейса (например, на переднем плане находится другая Activity).
  • Возобновлён - Fragment виден на экране и доступен для пользователя.

Посмотрите на таблицу коллбэков фрагмента, которые вызываются соответственно с изменением состояния Activity:

Давайте рассмотрим некоторые из них.

  • onAttach() - Fragment "прикрепляется" к Activity .
  • onCreate() - Fragment создаётся.
  • onCreateView() - вызывается для создания элементов интерфейса (например, инфлейта из XML).
  • onActivityCreated() - вызывается после отработки метода onCreate() в Activity .
  • onDestroyView() - вызывается, когда View , созданный в onCreateView "открепляется" от фрагмента.
  • onDetach() - Fragment "открепляется" от Activity .

В целом всё очень похоже на Activity , за исключением некоторых новых коллбэков.

Давайте же попробуем это на практике

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

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

  • Фрагмент с переключателями, которыми мы выбираем цвет.
  • Фрагмент, просто залитый выбранным цветом.

На телефоне это будет два экрана - на первом выбираем цвет, после чего запускается Activity с фрагментом, отображающим цвет.

На планшете будет лишь один экран с двухпанельным интерфейсом, прямо как в приведённом мною выше примере.

Сначала разберёмся, как мы можем создать фрагмент и добавить его в Activity .

Создайте новый проект, а в нём - новый фрагмент:

Public class SelectionFragment extends Fragment { public SelectionFragment() { } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } }

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

Также создадим файл с вёрсткой для фрагмента - fragment_selection.xml:

Теперь заинфлейтим лэйаут внутри метода onCreateView() :

@Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_selection, container, false); view.setBackgroundColor(Color.RED); return view; }

Я задал красный цвет, чтобы было понятно, где находится фрагмент.

Существует два способа добавления фрагмента в Activity:

  • Через XML. В этом случае нельзя будет удалить фрагмент в рантайме.
  • В рантайме.

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

Попробуем добавить фрагмент в activity_main.xml:

Запустите приложение:

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

В первую очередь заменим разметку лэйаута MainActivity на такую:

FrameLayout в данном случае будет служить контейнером для фрагмента.

Теперь в конце метода onCreate() в MainActivity добавим такой код:

SelectionFragment selectionFragment = new SelectionFragment(); FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction() .add(R.id.container, selectionFragment) .commit();

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

FragmentManager - специальный класс, через который происходит взаимодействие с фрагментами.

Обратите внимание: В Android SDK есть две реализации фрагментов: обычная и из Support-библиотеки v4 . Несмотря на то, что в большинстве примеров в интернете используется реализация из Support Library, на самом деле в наши дни использовать её необязательно, так как она была создана для работы фрагментов на более ранних версиях Android, чем 3.0.

Начиная с 3.0 можно использовать обычную реализацию.

Запустив приложение вы увидите, что результат остался тем же:

Создаём двухпанельный лэйаут

Итак, нам нужно будет два лейаута для MainActivity: один для телефонов , второй для планшетов .

Создадим лэйаут для планшета . Это делается так же, как и обычно с одним отличием:


Как видите, я выбрал квалификатор Smallest screen width и ввёл значение 600 . Таким образом, этот лэйаут будет использоваться только на тех устройствах, ширина экрана которых составляет хотя бы 600dp . Это примерно соответствует планшету с диагональю экрана 7 дюймов.

Стандартная вёрстка (для телефонов ) будет такой:

Поскольку в Android приветствуется максимальное использование декларативного подхода при создании интерфейсов, а, кроме того, нам не нужно будет удалять или заменять SelectionFragment , мы объявим его прямо в XML.

Вёрстка же для планшетов будет такой:

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

Так как мы теперь не добавляем SelectionFragment динамически, удалите весь связанный с ним код из onCreate() в MainActivity .

Также создайте новый эмулятор для планшета, например, такой:


Запустите на нём приложение:


Как видите, на планшете SelectionFragment занимает левые 30% экрана. Остальное место отведено для второго фрагмента, которого пока что нет.

Давайте продолжим - добавим переключатели для выбора цвета.

RadioButton и RadioGroup

RadioButton - компонент для создания переключателей. Поскольку RadioButton не должен использоваться в одиночку, существует также и лэйаут для него - RadioGroup .

RadioGroup унаследован от LinearLayout и содержит в себе несколько RadioButton . Он управляет эксклюзивностью выбора (ведь в единицу времени может быть выбран только один переключатель). Благодаря наследованию от LinearLayout , он может быть как вертикальным, так и горизонтальным.


Продолжение доступно на платных тарифах

А вместе с ним - проверка домашних заданий нашими менторами.

Это совсем недорого - всего от 0 ₽ в месяц!

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


Список перехватываемых методов фрагмента

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

onCreate — инициализация внутренних компонентов фрагмента сохранёнными данными.

onCreateView — формирование компонента для отображения.

onPause — обработка ситуации, когда фрагмент теряет фокус.

Описание всех методов по порядку их вызова:

onAttach — первое подсоединение фрагмента к активности. Сюда передаётся активность, к которой происходит подсоединение.

onCreate — инициализация фрагмента. Сюда передаются данные класса Bundle о последнем состоянии фрагмента в его предыдущей жизни, если таковая была, сохранённые ранее, например, в методе onSaveInstanceState, для восстановления этого состояния. На этот момент активность ещё находится в процессе создания.

onCreate View — формирование вида для отображения. Возвращает вид фрагмента. Может возвращать null для невизуальных компонентов. Сюда передаются данные класса Bundle о последнем состоянии фрагмента, а также контейнер активности, куда будет подключаться фрагмент и «надуватель» разметки.

on View Create d — вызывается, когда вид сформирован. Сюда передаётся сформированный вид и данные класса Bundle о последнем состоянии фрагмента. Используется для окончательной инициализации вида перед восстановлением сохранённого состояния. В этой точке вид ещё не прикреплён к фрагменту.

onActivityCreate d — окончательная инициализация. Вызывется когда метод onCreate() активности был возвращен. Активность создана, фрагмент в неё вставлен. Используется, например, для восстановления состояния фрагмента. Сюда передаются данные класса Bundle о последнем состоянии фрагмента.

onViewStateRestored — инициализация вида на основе сохранённого состояния. Вызывается, когда сохранённое состояние вида восстановлено.

onSaveInstanceState — сохранение состояния фрагмента. Срабатывает только в том случае, если фрагмент останавливается и может быть убит системой, но фактически ещё нужен. Это происходит, например, при вызове следующей активности, при нажатии кнопки «домой» а также в случае полного разрушения активности и создания её заново в результате изменения конфигурации устройства (смена языка, устройства ввода, ориентации экрана и т.п.). Объект класса Bundle, хранящий состояние активности, передаётся в методы onCreate, onPostCreate и onRestoreInstanceState. Внимание! Метод может быть вызван в любое время до onDestroy !

onDestroyView — вызывается при отсоединении вида от фрагмента. При следующем отображении фрагмента будет сформирован новый вид.

onDetach — вызывается перед отсоединением фрагмента от активности.