Фрагменты. Жизненный цикл Fragment. RadioButton. RadioGroup.

Урок 29 — Фрагменты. Жизненный цикл Fragment. RadioButton. RadioGroup.

Рассказывая о разработке под 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:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</FrameLayout>

Теперь заинфлейтим лэйаут внутри метода 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:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context="com.skillberg.fragments.MainActivity">

    <fragment
        android:id="@+id/selection_fragment"
        class="com.skillberg.fragments.SelectionFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

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

Фрагмент на экране телефона
Фрагмент на экране телефона

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

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context="com.skillberg.fragments.MainActivity">

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

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 можно использовать обычную реализацию.

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

Fragment добавленный в рантайме
Fragment добавленный в рантайме

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

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

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

Создание лэйаута для планшета
Создание лэйаута для планшета

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

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

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.skillberg.fragments.MainActivity">

    <fragment
        android:id="@+id/selection_fragment"
        class="com.skillberg.fragments.SelectionFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

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

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context="com.skillberg.fragments.MainActivity"
    android:baselineAligned="false">

    <fragment
        android:id="@+id/selection_fragment"
        class="com.skillberg.fragments.SelectionFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight=".3" />

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight=".7" />

</LinearLayout>

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

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

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

Эмулятор Nexus 9
Эмулятор Nexus 9

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

Двухпанельный интерфейс на планшете
Двухпанельный интерфейс на планшете

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

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

RadioButton и RadioGroup

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

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


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

Это недорого — всего от 440 ₽ в месяц!


ВЫБРАТЬ ТАРИФ


Продолжение доступно после регистрации

Все уроки на сайте доступны абсолютно бесплатно после регистрации.

Регистрация займёт меньше минуты ;)