Урок 105. Android 3. Fragments. Динамическая работа
Размещать статические фрагменты мы уже умеем. Но, ясно дело, что гораздо интереснее работать с ними динамически. Система позволяет нам добавлять, удалять и заменять фрагменты друг другом. При этом мы можем сохранять все эти манипуляции в BackStack и кнопкой Назад отменять. В общем, все удобно и красиво.
Создадим простое приложение с двумя фрагментами, которое будет уметь:
— добавлять первый фрагмент
— удалять первый фрагмент
— заменять первый фрагмент вторым фрагментом
— переключать режим сохранения в BackStack операций с фрагментами
Project name: P1051_FragmentDynamic
Build Target: Android 4.1
Application name: FragmentDynamic
Package name: ru.startandroid.develop.p1051fragmentdynamic
Create Activity: MainActivity
В strings.xml добавим строки:
Fragment 1 Fragment 2 Add Remove Replace add to Back Stack
Создаем фрагменты. Как мы помним из прошлого урока, для этого нам нужны будут layout-файлы и классы, наследующие android.app.Fragment
fragment1.xml:
fragment2.xml:
Fragment1.java:
package ru.startandroid.develop.p1051fragmentdynamic; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class Fragment1 extends Fragment < @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) < return inflater.inflate(R.layout.fragment1, null); >>
Fragment2.java:
package ru.startandroid.develop.p1051fragmentdynamic; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class Fragment2 extends Fragment < @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) < return inflater.inflate(R.layout.fragment2, null); >>
Все почти аналогично прошлому уроку, только убрали вызовы кучи lifecycle методов с логами.
Рисуем основное Activity.
Три кнопки для добавления, удаления и замены фрагментов. Чекбокс для включения использования BackStack. И FrameLayout – это контейнер, в котором будет происходить вся работа с фрагментами. Он должен быть типа ViewGroup. А элементы Fragment, которые мы использовали на прошлом уроке для размещения фрагментов, нам не нужны для динамической работы.
MainActivity.java:
package ru.startandroid.develop.p1051fragmentdynamic; import android.app.Activity; import android.app.FragmentTransaction; import android.os.Bundle; import android.view.View; import android.widget.CheckBox; public class MainActivity extends Activity < Fragment1 frag1; Fragment2 frag2; FragmentTransaction fTrans; CheckBox chbStack; @Override public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.main); frag1 = new Fragment1(); frag2 = new Fragment2(); chbStack = (CheckBox)findViewById(R.id.chbStack); >public void onClick(View v) < fTrans = getFragmentManager().beginTransaction(); switch (v.getId()) < case R.id.btnAdd: fTrans.add(R.id.frgmCont, frag1); break; case R.id.btnRemove: fTrans.remove(frag1); break; case R.id.btnReplace: fTrans.replace(R.id.frgmCont, frag2); default: break; >if (chbStack.isChecked()) fTrans.addToBackStack(null); fTrans.commit(); > >
В onCreate создаем пару фрагментов и находим чекбокс.
В onClick мы получаем менеджер фрагментов с помощью метода getFragmentManager. Этот объект является основным для работы с фрагментами. Далее, чтобы добавить/удалить/заменить фрагмент, нам необходимо использовать транзакции. Они аналогичны транзакциям в БД, где мы открываем транзакцию, производим операции с БД, выполняем commit. Здесь мы открываем транзакцию, производим операции с фрагментами (добавляем, удаляем, заменяем), выполняем commit.
Итак, мы получили FragmentManager и открыли транзакцию методом beginTransaction. Далее определяем, какая кнопка была нажата:
если Add, то вызываем метод add, в который передаем id контейнера (тот самый FrameLayout из main.xml) и объект фрагмента. В итоге, в контейнер будет помещен Fragment1
если Remove, то вызываем метод remove, в который передаем объект фрагмента, который хотим убрать. В итоге, фрагмент удалится с экрана.
если Replace, то вызываем метод replace, в который передаем id контейнера и объект фрагмента. В итоге, из контейнера удалится его текущий фрагмент (если он там есть) и добавится фрагмент, указанный нами.
Далее проверяем чекбокс. Если он включен, то добавляем транзакцию в BackStack. Для этого используем метод addToBackStack. На вход можно подать строку-тэг. Я передаю null.
Ну и вызываем commit, транзакция завершена.
Давайте смотреть, что получилось. Все сохраняем, запускаем приложение.

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

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

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

Выполняем те же операции: Add, Remove, Add, Replace. У нас добавится первый фрагмент, удалится первый фрагмент, добавится первый фрагмент, заменится вторым. В итоге мы снова видим второй фрагмент. Теперь жмем несколько раз кнопку Назад и наблюдаем, как выполняются операции, обратные тем, что мы делали. Когда транзакции, сохраненные в стеке закончатся, кнопка Назад закроет приложение.
Т.е. все достаточно просто и понятно. Скажу еще про пару интересных моментов.
Я в этом примере выполнял всего одну операцию в каждой транзакции. Но, разумеется, их может быть больше.
Когда мы удаляем фрагмент и не добавляем транзакцию в BackStack, то фрагмент уничтожается. Если же транзакция добавляется в BackStack, то, при удалении, фрагмент не уничтожается (onDestroy не вызывается), а останавливается (onStop).
В качестве самостоятельной работы: попробуйте немного изменить приложение и добавлять в один контейнер сразу два фрагмента. Возможно, результат вас удивит )
На следующем уроке:
— рассмотрим взаимодействие между Activity и ее фрагментами
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
Можно ли из фрагмента закрывать активити?
Задумано, чтобы при закрытии фрагмента закрывалась сразу и активити. Есть 2 варианта:
1) непосредственно в методе onDestroy() фрагмента вызывать activity.finish()
2) Передавать информацию активити, чтобы она самостоятельно finish()
В первом варианте меньше кода, но смущает момент, не много ли полномочий я даю фрагменту? Какой вариант приемлим?
- Вопрос задан более двух лет назад
- 163 просмотра
5 комментариев
Простой 5 комментариев
Как из фрагмента закрыть или удалить етот же фрагмент в активити? [закрыт]
Закрыт. Этот вопрос не по теме. Ответы на него в данный момент не принимаются.
Вопросы с просьбами помочь с отладкой («почему этот код не работает?») должны включать желаемое поведение, конкретную проблему или ошибку и минимальный код для её воспроизведения прямо в вопросе. Вопросы без явного описания проблемы бесполезны для остальных посетителей. См. Как создать минимальный, самодостаточный и воспроизводимый пример.
Закрыт 6 лет назад .
Я пробовал так : ((MainActivity)getActivity().getSupportFragmentManager().beginTransaction().remove(this); Но так ошибка.
Фрагменты вместо Активити: обработчик кнопки “назад”- часть 1
Во введении я перечислил проблемы при реализации навигации по экранам, используя Фрагменты в качестве функциональных экранов приложения.
Теперь я опишу, как обычно решаю первую проблему: отсутствие обработчика возврата в классе Фрагмента на предыдущий Фрагмент/Активити.
onBackPressed в классе Активити
В классе Активити есть метод, который переопределяют, чтобы ловить событие нажатия системной кнопки “Назад”:
@Override
public void onBackPressed()
// Какие-то действия, как, например, вывод сообщения: "Нажмите 'Назад' второй раз для выхода из приложения" super.onBackPressed();
>
Но в классе Фрагмента этого удобного метода нет, потому что Фрагменты задумывались как частичные представления, а не как замена Активити — то есть экраны в приложении.
Принцип решения
- Вводим интерфейс OnBackButtonListener , в котором определяем единственный метод boolean onBackPressed().
- Каждый класс Фрагмента, в котором нужно обработать событие нажатия кнопки “Назад”, должен реализовывать этот интерфейс.
- В методе onBackPressed() главной MainActivity реализуем логику по вызову собственного обработчика Фрагмента.
Пример приложения
Для этой части я реализовал пример. Видео к нему:
Видео демонстрации примера
Там есть всего три экрана и реализованы переходы между ними. На третьем последнем экране пользователь запускает действие. По факту, там ничего не делается и просто реализована пауза, во время которой отображается индикатор прогресса.
Я представил случай, что по какой-то причине, когда это действие выполняется, то необходимо дождаться его выполнения и не дать пользователю возможность закрыть Фрагмент по кнопке назад. В реальном приложении лучше не блокировать пользователя таким способом, но тут это используется для примера.
Пишем код
Полный код примера можно посмотреть в моём гитхабе:
poetofcode/FragmentsWithBackAction
Android project for a tutorial post. Contribute to poetofcode/FragmentsWithBackAction development by creating an…
А тут я приведу только самые важные части, по теме поста.
1. Интерфейс OnBackButtonListener
package ru.poetofcode.fragmentswithbackaction;
public interface OnBackButtonListener
boolean onBackPressed();
>
Метод onBackPressed() определён тут с результирующим параметром boolean , а не void , чтобы иметь возможность отменить стандартный ход выполнения onBackPressed в Активити (в моей реализации я обычно отдаю true , когда хочу запретить выход из Фрагмента). Таким образом, если это нужно, можно запретить возврат назад по нажатию кнопки. Например, часто можно увидеть в приложениях такой приём, где это используется — при нажатии кнопки “Назад” приложение выводит сообщение: “Нажмите назад ещё раз для выхода из приложения”.
2. Код шаблона главной Активити
xml version="1.0" encoding="utf-8"?>
FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" />
Сама MainActivity служит лишь контейнером для вложенных полноэкранных Фрагментов. Тут необходимо присвоить родительскому элементу id , я обычно добавляю в название слово container
3. Реализуем интерфейс OnBackButtonListener
Так выглядит код третьего фрагмента (на видео — тот, что запускает длительную операцию):
public class ThirdFragment extends Fragment implements OnBackButtonListener
boolean running = false;
// .
@Override
public boolean onBackPressed() if (running) Toast.makeText(getActivity(), "The operation is still in progress", Toast.LENGTH_LONG).show();
return true;
>
return false;
>
>
Я опустил тут часть, где реализуется само действие по нажатию кнопки, но нам тут важно, что в момент выполнения устанавливается флаг running в true . Таким образом, мы можем проверить — разрешать ли покидать Фрагмент. В противном случае — мы выводим пользователю сообщение.
4. Реализация onBackPressed() в главной Активити
Это самый важный пункт. Тут мы пользуемся FragmentManager ‘ом, для того, чтоб определить какой Фрагмент в данный момент является активным/текущим:
@Override
public void onBackPressed() int backStackCount = getSupportFragmentManager().getBackStackEntryCount();
// Находим текущий Фрагмент и вызваем его метод: onBackPressed()
if (backStackCount > 0) Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (currentFragment instanceof OnBackButtonListener)
OnBackButtonListener backListener = (OnBackButtonListener) currentFragment;
boolean actionResult = backListener.onBackPressed();
if (actionResult) return;
>
>
>
super.onBackPressed();
>
Тут нужно обратить внимание на две вещи:
- Текущий Фрагмент можно найти по идентификатору контейнера в шаблоне главной Активити. В данном случае по: R.id.fragment_container
- Проверяю, реализует ли класс интерфейс слушателя OnBackButtonListener и, если да, то вызываю непосредственно обработчик onBackPressed
В следующей части я займусь заголовком окна: рассмотрю как реализовать смену названия заголовка при переходе между Фрагментами, а также отображения стрелки “⬅” в верхнем AppBar ‘е приложения.
