Как создать фрагмент в android studio
Перейти к содержимому

Как создать фрагмент в android studio

  • автор:

Фрагменты

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

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

Фрагменты в Android

Для начала работы с фрагментами создадим новый проект с пустой MainActivity. И вначале создадим первый фрагмент. Но сразу стоит отметить, что не вся функциональность фрагментов по умолчанию может быть доступна в проекте, поскольку располагается в отдельной библиотеке — AndroidX Fragment library . И вначале необходимо подключить к проекту эту библиотеку в файле build.gradle .

Подключение фрагментов и AndroidX Fragment library в Android и Java

Найдем в нем секцию dependencies , которая выглядит по умолчанию примерно так:

dependencies

В ее начало добавим строку

implementation "androidx.fragment:fragment:1.3.6"

То есть в моем случае получится

dependencies

Подключение фрагментов и AndroidX Fragment library в Android и Java и Gradle

И затем нажмем на появившуюся ссылку Sync Now .

Фактически фрагмент — это обычный класс Java, который наследуется от класса Fragment . Однако как и класс Activity, фрагмент может использовать xml-файлы layout для определения графического интерфейса. И таким образом, мы можем добавить по отдельности класс Java, который представляет фрагмент, и файл xml для хранения в нем разметки интерфейса, который будет использовать фрагмент.

Итак, добавим в папку res/layout новый файл fragment_content.xml и определим в нем следующий код:

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

Теперь создадим сам класс фрагмента. Для этого добавим в одну папку с MainActivity новый класс. Для этого нажмем на папку правой кнопкой мыши и выберем в меню New -> Java Class . Назовем новый класс ContentFragment и определим у него следующее содержание:

package com.example.fragmentapp; import androidx.fragment.app.Fragment; public class ContentFragment extends Fragment < public ContentFragment()< super(R.layout.fragment_content); >>

Класс фрагмента должен наследоваться от класса Fragment .

Чтобы указать, что фрагмент будет использовать определенный xml-файл layout, идентификатор ресурса layout передается в вызов конструктора родительского класса (то есть класса Fragment).

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

Фрагменты в Android Studio и Java

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

Для использования фрагмента добавим его в MainActivity . Для этого изменим файл activity_main.xml , которая определяет интерфейс для MainActivity:

Для добавления фрамента применяется элемент FragmentContainerView . По сути FragmentContainerView представляет объект View, который расширяет класс FrameLayout и предназначен специально для работы с фрагментами. Собственно кроме фрагментов он больше ничего содержать не может.

Его атрибут android:name указывает на имя класса фрагмента, который будет использоваться. В моем случае полное имя класса фрагмента с учетов пакета com.example.fragmentapp.ContentFragment .

Код класса MainActivity остается тем же, что и при создании проекта:

package com.example.fragmentapp; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); >>

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

Создание фрагмента для Android и Java

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

Для этого нажмем на папку, где находится класс MainActivity , правой кнопкой мыши и в появившемся меню выберем New -> Fragment -> Fragment(Blank) :

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

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

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

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

Фрагмент определяет кнопку. Теперь добавим к этой кнопки некоторое действие. Для этого изменим класс ContentFragment:

package com.example.fragmentapp; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import java.util.Date; public class ContentFragment extends Fragment < public ContentFragment()< super(R.layout.fragment_content); >@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) < super.onViewCreated(view, savedInstanceState); Button updateButton = view.findViewById(R.id.updateButton); TextView updateBox = view.findViewById(R.id.dateTextView); updateButton.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View v) < String curDate = new Date().toString(); updateBox.setText(curDate); >>); > >

Здесь переопределен метод onViewCreated класса Fragment, который вызывается после создания объекта View для визуального интерфейса, который представляет данный фрагмент. Созданный объект View передается в качестве первого параметра. И далее мы можем получить конкретные элементы управления в рамках этого объекта View, в частности, TextView и Button, и выполнить с ними некоторые действия. В данном случае в обработчике нажатия кнопки в текстовом поле выводится текущая дата.

Добавление фрагмента в AndroidX Fragment Library и Java

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

Кроме определения фрагмента в xaml-файле интерфейса мы можем добавить его динамически в activity.

Для этого изменим файл activity_main.xml :

И также изменим класс MainActivity :

package com.example.fragmentapp; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (savedInstanceState == null) < getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container_view, ContentFragment.class, null) .commit(); >> >

Метод getSupportFragmentManager() возвращает объект FragmentManager , который управляет фрагментами.

Объект FragmentManager с помощью метода beginTransaction() создает объект FragmentTransaction .

FragmentTransaction выполняет два метода: add() и commit(). Метод add() добавляет фрагмент: add(R.id.fragment_container_view, new ContentFragment()) — первым аргументом передается ресурс разметки, в который надо добавить фрагмент (это определенный в activity_main.xml элемент androidx.fragment.app.FragmentContainerView ). И метод commit() подтвержает и завершает операцию добавления.

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

Фрагменты

Кот из фрагментов

Существует два основных подхода в использовании фрагментов.

Первый способ основан на замещении родительского контейнера. Создаётся стандартная разметка и в том месте, где будут использоваться фрагменты, размещается контейнер, например, FrameLayout. В коде контейнер замещается фрагментом. При использовании подобного сценария в разметке не используется тег fragment, так как его нельзя менять динамически. Также вам придётся обновлять ActionBar, если он зависит от фрагмента. Здесь показан такой пример.

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

Fragment

Fragment

Основные классы

Сами фрагменты наследуются от androidx.fragment.app.Fragment. Существует подклассы фрагментов: ListFragment, DialogFragment, PreferenceFragment, WebViewFragment и др. Не исключено, что число классов будет увеличиваться, например, появился ещё один класс MapFragment.

Для взаимодействия между фрагментами используется класс android.app.FragmentManager — специальный менеджер по фрагментам.

Как в любом офисе, спецманагер не делает работу своими руками, а использует помощников. Например, для транзакций (добавление, удаление, замена) используется класс-помощник android.app.FragmentTransaction.

Для сравнения приведу названия классов из библиотеки совместимости:

  • android.support.v4.app.FragmentActivity
  • android.support.v4.app.Fragment
  • android.support.v4.app.FragmentManager
  • android.support.v4.app.FragmentTransaction

Как видите, разница в одном классе, который я привёл первым. Он используется вместо стандартного Activity, чтобы система поняла, что придётся работать с фрагментами. На данный момент студия создаёт проект на основе ActionBarActivity, который является подклассом FragmentActivity.

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

В 2018 году Гугл объявила фрагменты из пакета androd.app устаревшими. Заменяйте везде на версию из библиотеки совместимости. В 2020 году уже используют пакет androidx.fragment.app.

В версии Support Library 27.1.0 появились новые методы requireActivity() и requireContext(), которые пригодятся при написании кода, когда требуется наличие активности и нужно избежать ошибки на null.

Общий алгоритм работы с фрагментами будет следующим:

У каждого фрагмента должен быть свой класс. Класс наследуется от класса Fragment или схожих классов, о которых говорилось выше. Это похоже на создание новой активности или нового компонента.

Также, как в активности, вы создаёте различные методы типа onCreate() и т.д. Если фрагмент имеет разметку, то используется метод onCreateView() — считайте его аналогом метода setContentView(), в котором вы подключали разметку активности. При этом метод onCreateView() возвращает объект View, который является корневым элементом разметки фрагмента.

Разметку для фрагмента можно создать программно или декларативно через XML.

Создание разметки для фрагмента ничем не отличается от создания разметки для активности. Вот отрывок кода из метода onCreateView():

 public class FirstFragment extends Fragment implements OnClickListener < @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) < View view = inflater.inflate(R.layout.first_fragment, container, false); Button nextButton = (Button) view.findViewById(R.id.button_first); nextButton.setOnClickListener(this); return view; >// . > 

Глядя на этот код, вы должные понять, что фрагмент использует разметку из файла 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().

 FragmentManager fragmentManager = getFragmentManager() fragmentManager.beginTransaction() .remove(fragment1) .add(R.id.fragment_container, fragment2) .show(fragment3) .hide(fragment4) .commit(); 

Аргументы фрагмента

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

  • Активность может создать фрагмент и установить аргументы для него
  • Активность может вызвать методы экземпляра фрагмента
  • Фрагмент может реализовать интерфейс, который будет использован в активности в виде слушателя

Фрагмент должен иметь только один пустой конструктор без аргументов. Но можно создать статический newInstance с аргументами через метод setArguments().

 public class CatFragment extends Fragment < public static CatFragment newInstance(int someInt, String someString) < CatFragment catFragment = new CatFragment(); Bundle args = new Bundle(); args.putInt("someInt", someInt); args.putString("SomeString", someString); catFragment.setArguments(args); return catFragment; >> 

Доступ к аргументам можно получить в методе onCreate() фрагмента:

 @Override public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); // Get back arguments int someInt = getArguments().getInt("someInt", 0); String someString = getArguments().getString("someString", ""); >

Динамически загружаем фрагмент в активность.

 FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); CatFragment catFragment = CatFragment.newInstance(5, "Васька"); ft.replace(R.id.your_placeholder, catFragment); ft.commit(); 

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

 public class CatFragment extends Fragment < public void sayMeow(String word) < // do something in fragment >> 

Вызываем метод в активности:

 public class MainActivity extends ActionBarActivity < @Override public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); CatFragment catFragment = (CatFragment) getSupportFragmentManager().findFragmentById(R.id.fragmentCat); catFragment.sayMeow("Жрать!"); >> 

Если фрагмент должен сообщить о своих действиях активности, то следует реализовать интерфейс.

 import android.content.Context; import android.support.v4.app.Fragment; import android.view.View; public class LinkListFragment extends Fragment < // Define the listener of the interface type // listener is the activity itself private OnLinkItemSelectedListener mListener; // Define the events that the fragment will use to communicate public interface OnLinkItemSelectedListener < public void onLinkItemSelected(String link); >// Store the listener that will have events fired once the fragment is attached @Override public void onAttach(Context context) < super.onAttach(context); if (context instanceof OnLinkItemSelectedListener) < mListener = (OnLinkItemSelectedListener) context; >else < throw new ClassCastException(context.toString() + " must implement MyListFragment.OnItemSelectedListener"); >> // Now we can fire the event when the user selects something in the fragment public void onSomeClick(View v) < mListener.onLinkItemSelected("some link"); >> 
 public class MainActivity extends ActionBarActivity implements LinkListFragment.OnLinkItemSelectedListener < DetailFragment fragment; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); fragment = (DetailFragment) getSupportFragmentManager() .findFragmentById(R.id.detailFragment); >// Now we can define the action to take in the activity when the fragment event fires @Override public void onLinkItemSelected(String link) < if (fragment != null && fragment.isInLayout()) < fragment.setText(link); >> > 

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

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

Чтобы добавить транзакцию в стек, вызовите метод FragmentTransaction.addToBackStack(String) перед завершением транзакции (commit). Строковый аргумент — опциональное имя для идентификации стека или null. Класс FragmentManager имеет метод popBackStack(), возвращающий предыдущее состояние стека по этому имени.

Если вы вызовете метод addToBackStack() при удалении или замещении фрагмента, то будут вызваны методы фрагмента onPause(), onStop(), onDestroyView().

Когда пользователь нажимает на кнопку возврата, то вызываются методы фрагмента onCreateView(), onActivityCreated(), onStart() и onResume().

Рассмотрим пример реагирования на кнопку Back в фрагменте без использования стека. Активность имеет метод onBackPressed(), который реагирует на нажатие кнопки. Мы можем в этом методе сослаться на нужный фрагмент и вызвать метод фрагмента.

 @Override public void onBackPressed() < super.onBackPressed(); // где-то ранее мы объявили фрагмент fragment1.backButtonWasPressed(); >

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

 public void backButtonWasPressed()

Более желательным вариантом является использование интерфейсов. В некоторых примерах с фрагментами такой приём используется.

Интеграция Action Bar/Options Menu

Фрагменты могут добавлять свои элементы в панель действий или меню активности. Сначала вы должны вызвать метод Fragment.setHasOptionsMenu() в методе фрагмента onCreate(). Затем нужно задать настройки для методов фрагмента onCreateOptionsMenu() и onOptionsItemSelected(), а также при необходимости для методов onPrepareOptionsMenu(), onOptionsMenuClosed(), onDestroyOptionsMenu(). Работа методов фрагмента ничем не отличается от аналогичных методов для активности.

В активности, которая содержит фрагмент, данные методы автоматически сработают.

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

Код для активности:

 public class MyActivity extends Activity < // . @Override public boolean onCreateOptionsMenu(Menu menu) < super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.activity_options, menu); return true; >@Override public boolean onOptionsItemSelected(MenuItem item) < switch (item.getItemId()) < case R.id.menu_activity_info: // Handle activity menu item return true; default: // Handle fragment menu items return super.onOptionsItemSelected(item); >> // . > 

Код для фрагмента:

 public class MyFragment extends Fragment < // . @Override public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setHasOptionsMenu(true); >@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) < inflater.inflate(R.menu.myfragment_options, menu); >@Override public boolean onOptionsItemSelected(MenuItem item) < switch (item.getItemId()) < case R.id.menu_first_info: // Handle fragment menu item return true; default: // Not one of ours. Perform default menu processing return super.onOptionsItemSelected(item); >> // . > 

Связь между фрагментом и активностью

Экземпляр фрагмента связан с активностью. Активность может вызывать методы фрагмента через ссылку на объект фрагмента. Доступ к фрагменту можно получить через методы findFragmentById() или findFragmentByTag().

Фрагмент в свою очередь может получить доступ к своей активности через метод Fragment.getActivity().

 View listView = getActivity().findViewById(R.id.list); 

Fragment (Фрагменты)

Кот из фрагментов

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

Зачем?

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

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

Фрагменты были представлены в API 11 (Android 3.0), но в целях совместимости была написана специальная библиотека Android Support library для старых устройств. Долгое время существовало два класса Fragment: для новых устройств и для старых устройств. Названия методов и классов были очень похожи, и разработчики часто путались, смешивая в одном проекте два разных несовместимых класса. Спустя некоторое время решили отказаться от странного разделения, класс для новых устройств признали устаревшим, а класс из библиотеки поддержки старых устройств сменил своё полное имя и вошёл в состав AndroidX.

При желании можно было продолжить писать приложения в старом стиле, отслеживая размеры экрана. Но такой код получится слишком сложным. Пришлось бы писать один код для переключения от одной активности к другой при использовании смартфона и другой код, когда взаимодействие между объектами происходит на одном экране в планшете. Чтобы устранить это противоречие, были придуманы фрагменты. Хотя там тоже придётся писать много кода.

Несколько слов о том, как проще воспринимать фрагмент. Считайте, что фрагмент — это тот же компонент как Button, TextView или LinearLayout с дополнительными возможностями. Фрагмент, как и кнопку, нужно поместить на экран активности. Но фрагмент является модульным компонентом и один и тот же фрагмент можно встроить в две разные активности. С кнопкой такой номер не пройдёт. Для каждой активности вы должны создать свою отдельную кнопку, даже если их нельзя будет отличить друг от друга.

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

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

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

Фрагмент может иметь свою разметку, а может обойтись без неё. Также у фрагмента есть свой жизненный цикл, во многом совпадающий с жизненным циклом активности. Пожалуй, это единственное сходство с активностью.

Имеются специальные виды фрагментов, заточенные под определённые задачи — ListFragment, DialogFragment и другие, которые изучим в других уроках.

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

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

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

Реакция разработчиков на появление фрагментов противоречива. Кто-то активно использует их в своих проектах, а кто-то их не переносит и использует альтернативные варианты. Похоже, в стане Гугла также идёт борьба между двумя группами программистов. Фрагменты постоянно развиваются, меняются и дорабатываются.

Первое знакомство

Для первого знакомства создадим стандартный проект на базе Empty Activity и вручную добавим фрагмент. Итак, после создания проекта выбираем из контекстного меню пакета New | Fragment | Fragment (Blank). В диалоговом окне мастера назначаем имя для фрагмента.

Fragment (Blank)

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

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

В res/values/strings.xml содержится ресурс hello_blank_fragment. Изменим текст на что-то понятное:

 Это фрагмент для отображения кота 

Приготовления закончены. Осталось добавить фрагмент в активность. Открываем activity_main.xml и добавляем новый элемент.

В элементе fragment в атрибуте android:name указываем полное имя класса фрагмента.

Можно запустить проект и увидеть фрагмент внутри активности.

Fragment

Жизненный цикл фрагмента

У фрагмента есть жизненный цикл, как у активности. Но число методов цикла гораздо больше. Откроем теперь файла класса фрагмента CatFragment и добавим новый код (выделено жирным).

 package ru.alexanderklimov.fragment import android.os.Bundle import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private const val ARG_PARAM1 = "param1" private const val ARG_PARAM2 = "param2" /** * A simple [Fragment] subclass. * Use the [CatFragment.newInstance] factory method to * create an instance of this fragment. */ class CatFragment : Fragment() < // TODO: Rename and change types of parameters private var param1: String? = null private var param2: String? = null override fun onAttach(context: Context) override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) Log.d(TAG, "onCreate") arguments?.let < param1 = it.getString(ARG_PARAM1) param2 = it.getString(ARG_PARAM2) >> override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? < Log.d(TAG, "onCreateView") // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_cat, container, false) > override fun onViewCreated(view: View, savedInstanceState: Bundle?) < super.onViewCreated(view, savedInstanceState) Log.d(TAG, "onViewCreated") >override fun onActivityCreated(savedInstanceState: Bundle?) < super.onActivityCreated(savedInstanceState) Log.d(TAG, "onActivityCreated") >override fun onStart() < super.onStart() Log.d(TAG, "onStart") >override fun onResume() < super.onResume() Log.d(TAG, "onResume") >override fun onPause() < super.onPause() Log.d(TAG, "onPause") >override fun onStop() < super.onStop() Log.d(TAG, "onStop") >override fun onDestroyView() < super.onDestroyView() Log.d(TAG, "onDestroyView") >override fun onDestroy() < super.onDestroy() Log.d(TAG, "onDestroy") >override fun onDetach() companion object < private const val TAG = "CatFragment" /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment CatFragment. */ // TODO: Rename and change types and number of parameters @JvmStatic fun newInstance(param1: String, param2: String) = CatFragment().apply < arguments = Bundle().apply < putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2) >> > > 

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

 package ru.alexanderklimov.fragment import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() < companion object < private const val TAG = "MainActivity" >override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.d(TAG, "onCreate") >override fun onStart() < super.onStart() Log.d(TAG, "onStart") >override fun onResume() < super.onResume() Log.d(TAG, "onResume") >> 

Снова запускаем проект и смотрим логи.

 D/CatFragment: onAttach D/CatFragment: onCreate D/CatFragment: onCreateView D/CatFragment: onViewCreated D/MainActivity: onCreate D/CatFragment: onActivityCreated D/CatFragment: onStart D/MainActivity: onStart D/MainActivity: onResume D/CatFragment: onResume 

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

Жизненный цикл фрагментов

Два отдельных фрагмента

Мы научились размещать один фрагмент на экране активности. Ничто не мешает разместить на экране несколько фрагментов, которые являются контейнерами для компонентов. Давайте совместим на одном экране два старых примера — «Hello Kitty» и «Счётчик ворон/котов».

Повторим шаги создания нового фрагмента через готовый шаблон Fragment (Blank) и создадим два новых фрагмента KittyFragment и CounterFragment.

Разметку для KittyFragment копируем из старого примера.

Код также копируем с небольшим отличием. Если в активности мы размещали основной код в методе onCreate(), то для фрагмента используем onViewCreated(). Код шаблона для экономии я опущу.

 package ru.alexanderklimov.fragment . class KittyFragment : Fragment() < . override fun onViewCreated(view: View, savedInstanceState: Bundle?) < super.onViewCreated(view, savedInstanceState) val helloTextView: TextView = view.findViewById(R.id.hello_text) val imageButton: ImageButton = view.findViewById(R.id.imageButton) imageButton.setOnClickListener < helloTextView.text = "Hello Kitty" >> > 

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

Разметка для фрагмента counter_fragment.xml.

Код для класса CounterFragment. Здесь также мы добавляем весь код в onViewCreated().

 package ru.alexanderklimov.fragment class CounterFragment : Fragment() < . override fun onViewCreated(view: View, savedInstanceState: Bundle?) < super.onViewCreated(view, savedInstanceState) val counterText: TextView = view.findViewById(R.id.counter_text) view.findViewById(R.id.plus_button).setOnClickListener < var counterValue = counterText.text.toString().toInt() counterText.text = (++counterValue).toString() >view.findViewById(R.id.minus_button).setOnClickListener < var counterValue = counterText.text.toString().toInt() if (counterValue >0) counterText.text = (--counterValue).toString() > > > 

Осталось разместить два фрагмента в activity_main.xml.

Запускаем проект и видим два приложения в одном. В верхней части мы здороваемся с котёнком, а в нижней считаем котов. Обратите внимание, что весь код сосредоточился в классах фрагментов, а в MainActivity нам вообще ничего не пришлось писать. Фрагменты работают независимо и мы можем писать код в разных модулях.

Создание фрагмента

Чтобы создать фрагмент, класс должен унаследовать от Android.App.Fragment , а затем переопределить метод OnCreateView . OnCreateView вызывается действием, в котором размещен фрагмент, когда приходит время отображать этот фрагмент на экране, и возвращает View . Типичный метод OnCreateView создает View , расширяя файл макета и присоединяя его к родительскому контейнеру. Характеристики контейнера важны, так как Android будет применять параметры макета родительского элемента к пользовательскому интерфейсу фрагмента. Проиллюстрируем это на примере.

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

Приведенный выше код расширит представление Resource.Layout.Example_Fragment и добавит его в качестве дочернего представления в контейнер ViewGroup .

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

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

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

  • Декларативно — фрагменты можно использовать декларативно в .axml файлах макета с помощью тега .
  • Программным образом — фрагменты также можно динамически создавать экземпляры с помощью FragmentManager API класса.

Программное использование через класс FragmentManager будет обсуждаться в этом руководстве позднее.

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

Чтобы добавить фрагмент через макет, следует использовать тег и указать нужный фрагмент по его атрибуту class или android:name . Следующий фрагмент кода демонстрирует, как с помощью атрибута class объявить fragment .

В следующем фрагменте кода показано, как объявить fragment с помощью атрибута android:name , который идентифицирует класс Fragment.

Во время создания действия Android создает экземпляр каждого фрагмента, указанного в файле макета, и добавляет вместо элемента Fragment представление, которое создается из OnCreateView . Фрагменты, добавленные в действие декларативно, являются статическими и сохраняются в действии до его уничтожения. Вы не сможете динамически заменить или удалить фрагмент в период существования действия, к которому он прикреплен.

Каждому фрагменту должен быть назначен уникальный идентификатор:

  • android:id — как и другие элементы пользовательского интерфейса в файле макета, это уникальный идентификатор.
  • android:tag — это уникальная строка.

Если не используется ни один из указанных выше методов, фрагмент принимает идентификатор представления контейнера. В следующем примере не назначен ни идентификатор android:id , ни идентификатор android:tag , поэтому Android назначает фрагменту идентификатор fragment_container .

Регистр имени пакета

Android не позволяет использовать прописные буквы в именах пакетов. Если имя пакета содержит прописную букву, при попытке расширить представление создается исключение. Впрочем, Xamarin.Android менее требователен и допускает прописные буквы в пространстве имен.

Например, оба следующих фрагмента кода будут нормально работать в Xamarin.Android, но второй из них приводит к исключению android.view.InflateException в приложении Java на чистом Android.

Жизненный цикл фрагмента

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

Flow diagram illustrating the Fragment lifecycle

Методы жизненного цикла при создании фрагмента

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

  • OnInflate() — вызывается при создании фрагмента в составе макета представления. Он может вызываться немедленно после декларативного создания фрагмента из XML-файла макета. Фрагмент еще не связан с действием, но ему из иерархии представлений передаются в качестве параметров значения Activity, Bundle и AttributeSet. Этот метод лучше всего использовать для анализа AttributeSet и сохранения любых атрибутов, которые могут впоследствии потребоваться фрагменту.
  • OnAttach() — вызывается после того, как фрагмент связан с действием. Это первый метод, который вызывается после готовности фрагмента к использованию. Обычно фрагмент не должен реализовывать конструктор или переопределять конструктор по умолчанию. Любые компоненты, которые нужны для работы фрагмента, следует инициализировать в этом методе.
  • OnCreate() — вызывается действием для создания фрагмента. В момент вызова этого метода иерархия представлений того действия, в котором размещается фрагмент, может быть готова еще не полностью, поэтому фрагмент не может полагаться на любые сегменты этой иерархии раньше следующих этапов жизненного цикла фрагмента. Например, не используйте этот метод для оптимизации или настройки пользовательского интерфейса приложения. Это самый ранний момент, когда фрагмент может начать сбор нужной информации. Фрагмент на этом этапе выполняется в потоке пользовательского интерфейса, поэтому не выполняйте здесь никаких длительных вычислений, а при необходимости выносите их в фоновый поток. Этот метод может быть пропущен, если вызывается SetRetainInstance(true). Этот вариант будет рассмотрен подробнее ниже.
  • OnCreateView() — создает представление для фрагмента. Этот метод вызывается в тот момент, когда завершается метод OnCreate() соответствующего действия. В этот момент уже можно безопасно взаимодействовать с иерархией представлений действия. Этот метод должен возвращать представление, которое будет использоваться этим фрагментом.
  • OnActivityCreated() — Вызывается после завершения действия Activity.OnCreate действием размещения. На этом этапе следует выполнять окончательную оптимизацию пользовательского интерфейса.
  • OnStart() — вызывается после возобновления содержащего действия. С этого момента фрагмент становится виден пользователю. Во многих случаях фрагменты содержат код, который мог бы находиться методе OnStart() соответствующего действия.
  • OnResume() — Это последний метод, который вызывается, прежде чем пользователь сможет взаимодействовать с фрагментом. В качестве примера кода, который можно выполнять в этом методе, можно предложить включение функций устройства, с которым может взаимодействовать пользователь, например камеры или системы позиционирования. Службы такого рода иногда требуют значительной энергии, поэтому приложению следует минимизировать их использование для продления времени работы от батареи.

Методы жизненного цикла при уничтожении фрагмента

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

  • OnPause() — пользователь больше не может взаимодействовать с фрагментом. Эта ситуация возникает, если операция в другом фрагменте изменяет этот фрагмент или если действие, в котором размещается фрагмент, приостановлено. Вполне возможно, что действие с этим фрагментом еще отображается на экране, то есть оно может быть полупрозрачным или занимать не весь экран. Активация этого метода является первым признаком того, что пользователь покидает этот фрагмент. Здесь фрагменту следует сохранить все нужные изменения.
  • OnStop() — Фрагмент больше не отображается. Возможно, остановилась работа соответствующего действия или операция фрагмента изменяет его состояние в действии. Этот обратный вызов имеет такое же назначение, как и Activity.OnStop.
  • OnDestroyView() — этот метод вызывается для очистки ресурсов, связанных с представлением. Он вызывается при уничтожении представления, с которым связан этот фрагмент.
  • OnDestroy() — этот метод вызывается, когда фрагмент больше не используется. Он все еще связан с действием, но уже не функционирует. В этом методе следует очистить все ресурсы, которые использует фрагмент, например используемый камерой ресурс SurfaceView. Этот метод может быть пропущен, если вызывается SetRetainInstance(true). Этот вариант будет рассмотрен подробнее ниже.
  • OnDetach() — Этот метод вызывается непосредственно перед тем, как фрагмент больше не связан с действием. В этот момент иерархия фрагмента уже не существует, и сейчас самое время освободить все ресурсы, которые использует фрагмент.

Использование SetRetainInstance

Фрагмент может указать, что его не нужно уничтожать полностью, если действие будет создано повторно. Для этой цели класс Fragment предоставляет метод SetRetainInstance . Если этому методу передать аргумент true , при восстановлении действия в нем будет размещен тот же экземпляр этого фрагмента. В таком случае будут вызваны все методы обратного вызова из жизненного цикла фрагмента, кроме OnCreate и OnDestroy . Этот процесс иллюстрируется на схеме жизненного цикла выше (обозначен зеленым пунктиром).

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

Фрагменты могут сохранять и восстанавливать свое состояние в течение своего жизненного цикла, используя экземпляр Bundle . Bundle позволяет фрагменту сохранять данные как пары «ключ — значение», что удобно для хранения несложных, для которых не требуется много памяти. Фрагмент может сохранить состояние, вызвав OnSaveInstanceState .

public override void OnSaveInstanceState(Bundle outState)

При создании нового экземпляра фрагмента состояние, сохраненное в Bundle , становится доступным новому экземпляру через методы OnCreate , OnCreateView и OnActivityCreated нового экземпляра. В следующем примере демонстрируется извлечение значения current_choice из Bundle .

public override void OnActivityCreated(Bundle savedInstanceState) < base.OnActivityCreated(savedInstanceState); if (savedInstanceState != null) < _currentCheckPosition = savedInstanceState.GetInt("current_choice", 0); >> 

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

Поскольку элементу управления EditText назначен идентификатор id , этот фрагмент автоматически сохраняет данные в мини-приложении при вызове OnSaveInstanceState .

Ограничения при использовании Bundle

Несмотря на простоту сохранения временных данных с помощью OnSaveInstanceState , этот метод имеет ряд ограничений.

  • Если фрагмент не добавлен в обратный стек, состояние фрагмента не будет восстановлено при нажатии пользователем кнопки Назад.
  • Если для сохранения данных используется Bundle, эти данные сериализуются. Это может приводить к задержкам в обработке.

Участие в работе меню

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

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

public override void OnCreateOptionsMenu(IMenu menu, MenuInflater menuInflater)

Меню в предыдущем фрагменте кода расширяется из представленного ниже кода XML, который хранится в файле menu_fragment_vehicle_list.xml .

Затем фрагмент должен вызывать SetHasOptionsMenu(true) . Вызов этого метода сообщает платформе Android, что фрагмент намерен добавить пункты меню в существующее меню параметров. Если не вызвать этот метод, пункты меню из фрагмента не добавляются в меню параметров соответствующего действия. Обычно этот процесс выполняется в методе жизненного цикла OnCreate() , как показано в фрагменте кода ниже.

public override void OnCreate(Bundle savedState)

На следующем снимке экрана представлено, как будет выглядеть созданное меню.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *