- Multiple back stacks
- A deep dive into what actually went into this feature
- The joys of the system back button
- Multiple back stacks in Fragments
- Paying down our technical debts in Fragments
- Android 4.2: поведение заднего стека с вложенными фрагментами
- пример кода с getChildFragmentManager ():
- пример кода с getFragmentManager ():
- Фрагменты
- Основные классы
- FragmentManager
- Методы транзакции
- Аргументы фрагмента
- Управление стеком фрагментов
- Интеграция Action Bar/Options Menu
- Связь между фрагментом и активностью
Multiple back stacks
A deep dive into what actually went into this feature
If a ‘back stack’ is a set of screens that you can navigate back through via the system back button, ‘multiple back stacks’ is just a bunch of those, right? Well, that’s exactly what we’ve done with the multiple back stack support added in Navigation 2.4.0-alpha01 and Fragment 1.4.0-alpha01!
The joys of the system back button
Whether you’re using Android’s new gesture navigation system or the traditional navigation bar, the ability for users to go ‘back’ is a key part to the user experience on Android and doing that right is an important part to making your app feel like a natural part of the ecosystem.
In the simplest cases, the system back button just finishes your activity. While in the past you might have been tempted to override the onBackPressed() method of your activity to customize this behavior, it is 2021 and that is totally unnecessary. Instead, there are APIs for custom back navigation in the OnBackPressedDispatcher . This is actually the same API that FragmentManager and NavController already plug into.
That means when you use either Fragments or Navigation, they use the OnBackPressedDispatcher to ensure that if you’re using their back stack APIs, the system back button works to reverse each of the screens that you’ve pushed onto the back stack.
Multiple back stacks doesn’t change these fundamentals. The system back button is still a one directional command — ‘go back’. This has a profound effect on how the multiple back stack APIs work.
Multiple back stacks in Fragments
At the surface level, the support for multiple back stacks is deceptively straightforward, but requires a bit of an explanation of what actually is the ‘fragment back stack’. The FragmentManager ’s back stack isn’t made up of fragments, but instead is made up of fragment transactions. Specifically, the ones that have used the addToBackStack(String name) API.
This means when you commit() a fragment transaction with addToBackStack() , the FragmentManager is going to execute the transaction by going through and executing each of the operations (the replace , etc.) that you specified on the transaction, thus moving each fragment through to its expected state. FragmentManager then holds onto that transaction as part of its back stack.
When you call popBackStack() (either directly or via FragmentManager ’s integration with the system back button), the topmost transaction on the fragment back stack is reversed — an added fragment is removed, a hidden fragment is shown, etc. This puts the FragmentManager back into the same state that it was before the fragment transaction was initially committed.
Note: I cannot stress this enough, but you absolutely should never interleave transactions with addToBackStack() and transactions without in the same FragmentManager : transactions on your back stack are blissfully unaware of non-back stack changing fragment transactions — swapping things out from underneath those transactions makes that reversal when you pop a much more dicey proposition.
This means that popBackStack() is a destructive operation: any added fragment will have its state destroyed when that transaction is popped. This means you lose your view state, any saved instance state, and any ViewModel instances you’ve attached to that fragment are cleared. This is the main difference between that API and the new saveBackStack() . saveBackStack() does the same reversal that popping the transaction does, but it ensures that the view state, saved instance state, and ViewModel instances are all saved from destruction. This is how the restoreBackStack() API can later recreate those transactions and their fragments from the saved state and effectively ‘redo’ everything that was saved. Magic!
This didn’t come without paying down a lot of technical debt though.
Paying down our technical debts in Fragments
While fragments have always saved the Fragment’s view state, the only time that a fragment’s onSaveInstanceState() would be called would be when the Activity’s onSaveInstanceState() was called. To ensure that the saved instance state is saved when calling saveBackStack() , we need to also inject a call to onSaveInstanceState() at the right point in the fragment lifecycle transitions. We can’t call it too soon (your fragment should never have its state saved while it is still STARTED ), but not too late (you want to save the state before the fragment is destroyed).
This requirement kicked off a process to fix how FragmentManager moves to state to make sure there’s one place that manages moving a fragment to its expected state and handles re-entrant behavior and all the state transitions that go into fragments.
35 changes and 6 months into that restructuring of fragments, it turned out that postponed fragments were seriously broken, leading to a world where postponed transactions were left floating in limbo — not actually committed and not actually not committed. Over 65 changes and another 5 months later, and we had completely rewritten most of the internals of how FragmentManager manages state, postponed transitions, and animations. That effort is covered in more detail in my previous blog post:
Источник
Android 4.2: поведение заднего стека с вложенными фрагментами
В Android 4.2 библиотека поддержки получила поддержку вложенных фрагментов, см. Здесь . Я поигрался с ним и обнаружил интересное поведение / ошибку в отношении заднего стека и getChildFragmentManager () . При использовании getChildFragmentManager () и addToBackStack (String name) при нажатии кнопки возврата система не спускается по стеку назад к предыдущему фрагменту. С другой стороны, при использовании getFragmentManager () и addToBackStack (String name) при нажатии кнопки возврата система возвращается к предыдущему фрагменту.
Для меня такое поведение неожиданно. Нажав кнопку «Назад» на моем устройстве, я ожидаю, что последний добавленный фрагмент в задний стек будет извлечен, даже если фрагмент был добавлен в задний стек в диспетчере дочерних фрагментов.
Это правильное поведение? Это ошибка? Есть ли способ решить эту проблему?
пример кода с getChildFragmentManager ():
пример кода с getFragmentManager ():
Это решение может быть лучшей версией ответа @Sean:
Опять же, я подготовил это решение на основе ответа @Sean выше.
Как сказал @ AZ13, это решение возможно только в ситуациях дочерних фрагментов одного уровня. В случае с многоуровневыми фрагментами работы становятся немного сложными, поэтому я рекомендую попробовать это решение только в том возможном случае, который я сказал. знак равно
Примечание. Поскольку getFragments метод теперь является частным, это решение не сработает. Вы можете проверить комментарии по ссылке, которая предлагает решение этой ситуации.
Для обходного пути я успешно использовал (как было предложено в комментариях):
Настоящий ответ на этот вопрос находится в функции транзакции фрагмента, называемой setPrimaryNavigationFragment.
Вы должны установить эту функцию для исходного родительского фрагмента, когда активность добавляет его. У меня есть функция replaceFragment внутри моей активности, которая выглядит так:
Это дает вам такое же поведение, как если бы вы возвращались с обычного фрагмента B обратно на фрагмент A, за исключением того, что теперь это происходит и с дочерними фрагментами!
Это решение может быть лучшей версией ответа @ismailarilik:
Версия вложенного фрагмента
С этим ответом он будет обрабатывать рекурсивную обратную проверку и давать каждому фрагменту возможность переопределить поведение по умолчанию. Это означает, что у вас может быть фрагмент, на котором размещен ViewPager, что-то особенное, например, прокрутка к странице, которая используется в качестве заднего стека, или прокрутка до домашней страницы, а затем при следующем обратном нажатии на выход.
Добавьте это в свою Activity, которая расширяет AppCompatActivity.
Добавьте это в свой BaseFragment или в класс, от которого вы можете унаследовать все свои фрагменты.
Этот код будет перемещаться по дереву менеджеров фрагментов и возвращать последний из добавленных фрагментов, в котором есть фрагменты, которые он может выскочить из стека:
Причина в том, что ваше действие происходит от FragmentActivity, который обрабатывает нажатие клавиши BACK (см. Строку 173 FragmentActivity .
В нашем приложении я использую ViewPager (с фрагментами), и каждый фрагмент может иметь вложенные фрагменты. Я справился с этим следующим образом:
- определение интерфейса OnBackKeyPressedListener с помощью одного метода void onBackKeyPressed()
- реализовал этот интерфейс в «верхних» фрагментах, которые показывает ViewPager
- переопределение onKeyDown и обнаружение нажатия BACK и вызов onBackKeyPressed в текущем активном фрагменте в пейджере просмотра.
Также обратите внимание, что я использую getChildFragmentManager() в фрагментах для правильного размещения фрагментов. Вы можете увидеть обсуждение и объяснение в этом посте разработчиков Android .
если вы используете фрагмент во фрагменте, используйте getChildFragmentManager()
если вы используете дочерний фрагмент и замените его, используйте getParentFragmentManager()
если оба не работают, попробуйте это getActivity().getSupportFragmentManager()
Я смог обработать фрагмент обратного стека, добавив к родительскому фрагменту этот метод в методе onCreate View () и передав корневое представление.
Метод isEnableFragmentBackStack () — это логический флаг, позволяющий узнать, когда я нахожусь на основном или следующем фрагменте.
Убедитесь, что при фиксации фрагмента, который должен иметь стек, вы должны добавить метод addToBackstack.
Это решение может быть лучше, поскольку оно проверяет все уровни вложенных фрагментов:
в своей деятельности onBackPressed вы можете просто назвать это так:
Спасибо всем за помощь, у меня работает эта (измененная версия):
ПРИМЕЧАНИЕ. Возможно, вы также захотите установить цвет фона этих вложенных фрагментов в соответствии с цветом фона окна темы приложения, поскольку по умолчанию они прозрачны. В некоторой степени выходит за рамки этого вопроса, но это достигается путем разрешения атрибута android.R.attr.windowBackground и установки фона представления фрагмента на этот идентификатор ресурса.
Более 5 лет и этот вопрос по-прежнему актуален. Если вы не хотите использовать fragmentManager.getFragments () из-за его ограничений. Расширьте и используйте следующие классы:
NestedFragmentActivity.java
NestedFragment.java
Объяснение:
Каждое действие и фрагмент, расширенный из вышеупомянутых классов, управляет своим собственным обратным стеком для каждого из своих дочерних элементов, дочерних элементов детей и т. Д. Backstack — это просто запись тегов / идентификаторов «активного фрагмента». Поэтому предостережение — всегда предоставлять тег и / или идентификатор для вашего фрагмента.
При добавлении в backstack в childFragmentManager вам также потребуется вызвать addToParentBackStack (). Это гарантирует, что тег / идентификатор фрагмента будет добавлен в родительский фрагмент / действие для последующих всплывающих окон.
Источник
Фрагменты
Существует два основных подхода в использовании фрагментов.
Первый способ основан на замещении родительского контейнера. Создаётся стандартная разметка и в том месте, где будут использоваться фрагменты, размещается контейнер, например, FrameLayout. В коде контейнер замещается фрагментом. При использовании подобного сценария в разметке не используется тег fragment, так как его нельзя менять динамически. Также вам придётся обновлять ActionBar, если он зависит от фрагмента. Здесь показан такой пример.
Второй вариант — используются отдельные разметки для телефонов и планшетов, которые можно разместить в разных папках ресурсов. Например, если в планшете используется двухпанельная разметка с двумя фрагментами на одной активности, мы используем эту же активность для телефона, но подключаем другую разметку, которая содержит один фрагмент. Когда нам нужно переключиться на второй фрагмент, то запускаем вторую активность.
Второй подход является наиболее гибким и в целом предпочтительным способом использования фрагментов. Активность проверяет в каком режиме (свои размеры) он запущен и использует разную разметку из ресурсов. Графически это выглядит следующим образом.
Основные классы
Сами фрагменты наследуются от 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():
Глядя на этот код, вы должные понять, что фрагмент использует разметку из файла res/layout/first_fragment.xml, которая содержит кнопку с идентификатором android:id=»@+id/button_first». Здесь также прослеживается сходство с подключением компонентов в активности. Обратите внимание, что перед методом findViewById() используется view, так как этот метод относится к компоненту, а не к активности, как мы обычно делали в программах, когда просто опускали имя активности. Т.е. в нашем случае мы ищем ссылку на кнопку не среди разметки активности, а внутри разметки самого фрагмента.
Нужно помнить, что в методе inflate() последний параметр должен иметь значение false в большинстве случаев.
FragmentManager
Класс FragmentManager имеет два метода, позволяющих найти фрагмент, который связан с активностью:
findFragmentById(int id) Находит фрагмент по идентификатору findFragmentByTag(String tag) Находит фрагмент по заданному тегу
Методы транзакции
Мы уже использовали некоторые методы класса FragmentTransaction. Познакомимся с ними поближе
add() Добавляет фрагмент к активности remove() Удаляет фрагмент из активности replace() Заменяет один фрагмент на другой hide() Прячет фрагмент (делает невидимым на экране) show() Выводит скрытый фрагмент на экран detach() (API 13) Отсоединяет фрагмент от графического интерфейса, но экземпляр класса сохраняется attach() (API 13) Присоединяет фрагмент, который был отсоединён методом detach()
Методы remove(), replace(), detach(), attach() не применимы к статичным фрагментам.
Перед началом транзакции нужно получить экземпляр FragmentTransaction через метод FragmentManager.beginTransaction(). Далее вызываются различные методы для управления фрагментами.
В конце любой транзакции, которая может состоять из цепочки вышеперечисленных методов, следует вызвать метод commit().
Аргументы фрагмента
Фрагменты должны сохранять свою модульность и не должны общаться друг с другом напрямую. Если один фрагмент хочет докопаться до другого, он должен сообщить об этом своему менеджеру активности, а он уже передаст просьбу другому фрагменту. И наоборот. Это сделано специально для того, чтобы было понятно, что менеджер тут главный и он не зря зарплату получает. Есть три основных способа общения фрагмента с активностью.
- Активность может создать фрагмент и установить аргументы для него
- Активность может вызвать методы экземпляра фрагмента
- Фрагмент может реализовать интерфейс, который будет использован в активности в виде слушателя
Фрагмент должен иметь только один пустой конструктор без аргументов. Но можно создать статический newInstance с аргументами через метод setArguments().
Доступ к аргументам можно получить в методе onCreate() фрагмента:
Динамически загружаем фрагмент в активность.
Если активность должна выполнить какую-то операцию в фрагменте, то самый простой способ — задать нужный метод в фрагменте и вызвать данный метод через экземпляр фрагмента.
Вызываем метод в активности:
Если фрагмент должен сообщить о своих действиях активности, то следует реализовать интерфейс.
Управление стеком фрагментов
Фрагменты, как и активности, могут управляться кнопкой Back. Вы можете добавить несколько фрагментов, а потом через кнопку Back вернуться к первому фрагменту. Если в стеке не останется ни одного фрагмента, то следующее нажатие кнопки закроет активность.
Чтобы добавить транзакцию в стек, вызовите метод FragmentTransaction.addToBackStack(String) перед завершением транзакции (commit). Строковый аргумент — опциональное имя для идентификации стека или null. Класс FragmentManager имеет метод popBackStack(), возвращающий предыдущее состояние стека по этому имени.
Если вы вызовете метод addToBackStack() при удалении или замещении фрагмента, то будут вызваны методы фрагмента onPause(), onStop(), onDestroyView().
Когда пользователь нажимает на кнопку возврата, то вызываются методы фрагмента onCreateView(), onActivityCreated(), onStart() и onResume().
Рассмотрим пример реагирования на кнопку Back в фрагменте без использования стека. Активность имеет метод onBackPressed(), который реагирует на нажатие кнопки. Мы можем в этом методе сослаться на нужный фрагмент и вызвать метод фрагмента.
Теперь в классе фрагмента прописываем метод с нужным кодом.
Более желательным вариантом является использование интерфейсов. В некоторых примерах с фрагментами такой приём используется.
Интеграция Action Bar/Options Menu
Фрагменты могут добавлять свои элементы в панель действий или меню активности. Сначала вы должны вызвать метод Fragment.setHasOptionsMenu() в методе фрагмента onCreate(). Затем нужно задать настройки для методов фрагмента onCreateOptionsMenu() и onOptionsItemSelected(), а также при необходимости для методов onPrepareOptionsMenu(), onOptionsMenuClosed(), onDestroyOptionsMenu(). Работа методов фрагмента ничем не отличается от аналогичных методов для активности.
В активности, которая содержит фрагмент, данные методы автоматически сработают.
Если активность содержит собственные элементы панели действий или меню, то следует позаботиться, чтобы они не мешали вызовам методам фрагментов.
Код для активности:
Код для фрагмента:
Связь между фрагментом и активностью
Экземпляр фрагмента связан с активностью. Активность может вызывать методы фрагмента через ссылку на объект фрагмента. Доступ к фрагменту можно получить через методы findFragmentById() или findFragmentByTag().
Источник