- Полный список
- Android — ViewPager2 — заменяем фрагменты на лету (программно)
- Задача
- Результат
- Дисклеймер (не читать, вода)
- Решение
- Верстка
- Интерфейсы
- Связь активити и ViewPager2
- Адаптер для ViewPager2 — всё здесь
- Пример использования
- Мысли в слух
- Fragment transactions
- Kotlin
- Kotlin
- Allow reordering of fragment state changes
- Kotlin
- Adding and removing fragments
- Kotlin
- Commit is asynchronous
- Operation ordering is significant
- Kotlin
- Limit the fragment’s lifecycle
- Showing and hiding fragment’s views
- Attaching and detaching 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 добавим строки:
Создаем фрагменты. Как мы помним из прошлого урока, для этого нам нужны будут layout-файлы и классы, наследующие android.app.Fragment
Все почти аналогично прошлому уроку, только убрали вызовы кучи lifecycle методов с логами.
Рисуем основное Activity.
Три кнопки для добавления, удаления и замены фрагментов. Чекбокс для включения использования BackStack. И FrameLayout – это контейнер, в котором будет происходить вся работа с фрагментами. Он должен быть типа ViewGroup. А элементы Fragment, которые мы использовали на прошлом уроке для размещения фрагментов, нам не нужны для динамической работы.
В onCreate создаем пару фрагментов и находим чекбокс.
В onClick мы получаем менеджер фрагментов с помощью метода getFragmentManager. Этот объект является основным для работы с фрагментами. Далее, чтобы добавить/удалить/заменить фрагмент, нам необходимо использовать транзакции. Они аналогичны транзакциям в БД, где мы открываем транзакцию, производим операции с БД, выполняем commit. Здесь мы открываем транзакцию, производим операции с фрагментами (добавляем, удаляем, заменяем), выполняем commit.
Итак, мы получили FragmentManager и открыли транзакцию методом beginTransaction. Далее определяем, какая кнопка была нажата:
если Add, то вызываем метод add, в который передаем id контейнера (тот самый FrameLayout из main.xml) и объект фрагмента. В итоге, в контейнер будет помещен Fragment1
если Remove, то вызываем метод remove, в который передаем объект фрагмента, который хотим убрать. В итоге, фрагмент удалится с экрана.
если Replace, то вызываем метод replace, в который передаем id контейнера и объект фрагмента. В итоге, из контейнера удалится его текущий фрагмент (если он там есть) и добавится фрагмент, указанный нами.
Далее проверяем чекбокс. Если он включен, то добавляем транзакцию в BackStack. Для этого используем метод addToBackStack. На вход можно подать строку-тэг. Я передаю null.
Ну и вызываем commit, транзакция завершена.
Давайте смотреть, что получилось. Все сохраняем, запускаем приложение.
появился первый фрагмент.
Еще раз добавим первый фрагмент – жмем Add. И жмем Replace
первый фрагмент заменился вторым.
Жмем кнопку Назад. Приложение закрылось, т.к. все эти операции с фрагментами не сохранялись в BackStack. Давайте используем эту возможность.
Снова запускаем приложение и включаем чекбокс add to Back Stack
Выполняем те же операции: Add, Remove, Add, Replace. У нас добавится первый фрагмент, удалится первый фрагмент, добавится первый фрагмент, заменится вторым. В итоге мы снова видим второй фрагмент. Теперь жмем несколько раз кнопку Назад и наблюдаем, как выполняются операции, обратные тем, что мы делали. Когда транзакции, сохраненные в стеке закончатся, кнопка Назад закроет приложение.
Т.е. все достаточно просто и понятно. Скажу еще про пару интересных моментов.
Я в этом примере выполнял всего одну операцию в каждой транзакции. Но, разумеется, их может быть больше.
Когда мы удаляем фрагмент и не добавляем транзакцию в BackStack, то фрагмент уничтожается. Если же транзакция добавляется в BackStack, то, при удалении, фрагмент не уничтожается (onDestroy не вызывается), а останавливается (onStop).
В качестве самостоятельной работы: попробуйте немного изменить приложение и добавлять в один контейнер сразу два фрагмента. Возможно, результат вас удивит )
На следующем уроке:
— рассмотрим взаимодействие между Activity и ее фрагментами
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Android — ViewPager2 — заменяем фрагменты на лету (программно)
Вдруг вам надо листать фрагменты через ViewPager2 и при этом подменять их динамически. Например, чтобы уйти «глубже» — пользователь из фрагмента «Главные настройки» переходит во фрагмент «Выбор языка». При этом новый фрагмент должен отобразиться на месте главного фрагмента. А потом пользователь еще и захочет вернуться обратно.
список Fragment-ов (например, разделы анкеты или многостраничный раздел настроек)
Kotlin (Java), Android собственно
Задача
Необходимо заменять фрагменты динамически — на месте n-ого фрагмента должен появится другой. При этом все остальные страницы не должны терять своего состояния.
Есть три фрагмента. Первый — список любимых тарелок, второй — экран с сообщениями, третий — настройки.
Пользователь на первом экране выбирает тарелку -> меняем первый фрагмент на фрагмент «Информация о тарелке»
Пользователь листает свайпами или выбирает через TabLayout третий экран — «Настройки», выбирает раздел «Выбор языка» -> меняем третий фрагмент на фрагмент «Выбор языка»
Теперь пользователь, не выходя из выбора языка, листает до первого фрагмента. Он должен увидеть информацию о тарелке, а не список любимых тарелок.
Итак, пытаемся сдружить ArrayMap и ViewPager2.
Результат
Дисклеймер (не читать, вода)
Пишу, потому что найти хороших решений по этой задаче не смог, хотя вроде пытался усердно.
Код местами специально упрощен — не люблю, когда в статьях добавляют десятки строк, не касающихся решения описываемой задачи, только ради соблюдения всех правил. Так что: строковые ресурсы — вынести, вью модели для фрагментов — добавить, пару уровней абстракций — ну вы поняли
Весь проект — https://github.com/IDolgopolov/ViewPager2_FragmentReplacer
Иногда буду использовать слово «страницы» — имею ввиду позицию, на которой отображается фрагмент во ViewPager. Например, «Список любимых тарелок», «Экран сообщений» и «Настройки» — три страницы. Фрагмент «Выбор языка» заменяет третью страницу.
Решение
Сначала ничего интересного, просто для общего ознакомления
Верстка
Интерфейсы
Опишем функции, которые нам понадобятся для смены фрагментов
Все фрагменты, добавляемые во ViewPagerAdapter (MyViewPager2Adapter описан ниже), будут наследоваться от BaseFragment.
Определим в нем переменные для хранения:
pageId — уникальный номер страницы. Может быть любым числом, но главное — уникальным и большим, чем у вас позиций страниц (pageId > PAGE_COUNT — 1), иначе будут баги из-за метода getItemId()
pagePos — номер странице, на которой будет отображаться фрагмент во ViewPager (начиная с 0, естественно)
fragmentReplacer — ссылка на ViewPagerAdapter (MyViewPager2Adapter реализует FragmentReplacer)
Связь активити и ViewPager2
Адаптер для ViewPager2 — всё здесь
В этом классе будут реализован интерфейс FragmentReplacer и переопределены несколько классов FragmentStateAdapter. Это и позволит менять фрагменты на лету.
Весь код класса, который ниже разбираю подробно
В первую очередь переопределим четыре метода FragmentStateAdapter:
Теперь две функции, которые позволяют подменять фрагменты
Пример использования
Все готово — у каждого фрагмента есть ссылка на адаптер, значит есть возможность заменить фрагмент на любой странице, а самое главное (наиболее используемое на практике) — есть возмжность заменить самого себя.
Например, фрагмент, по умолчанию, отображаемый на первой странице:
Мысли в слух
Аккуратно, если фрагменты тяжелые, — надо задуматься об очищении mapOfFragment.
Возможно, стоит хранить Class вместо BaseFragment. Но тогда придется инициализировать их каждый раз в createFragment(). Меньше памяти, больше времени. Что думаете?
Источник
Fragment transactions
At runtime, a FragmentManager can add, remove, replace, and perform other actions with fragments in response to user interaction. Each set of fragment changes that you commit is called a transaction, and you can specify what to do inside the transaction using the APIs provided by the FragmentTransaction class. You can group multiple actions into a single transaction—for example, a transaction can add or replace multiple fragments. This grouping can be useful for when you have multiple sibling fragments displayed on the same screen, such as with split views.
You can save each transaction to a back stack managed by the FragmentManager , allowing the user to navigate backward through the fragment changes—similar to navigating backward through activities.
You can get an instance of FragmentTransaction from the FragmentManager by calling beginTransaction() , as shown in the following example:
Kotlin
The final call on each FragmentTransaction must commit the transaction. The commit() call signals to the FragmentManager that all operations have been added to the transaction.
Kotlin
Allow reordering of fragment state changes
Each FragmentTransaction should use setReorderingAllowed(true) :
Kotlin
For behavior compatibility, the reordering flag is not enabled by default. It is required, however, to allow FragmentManager to properly execute your FragmentTransaction , particularly when it operates on the back stack and runs animations and transitions. Enabling the flag ensures that if multiple transactions are executed together, any intermediate fragments (i.e. ones that are added and then immediately replaced) do not go through lifecycle changes or have their animations or transitions executed. Note that this flag affects both the initial execution of the transaction and reversing the transaction with popBackStack() .
Adding and removing fragments
To add a fragment to a FragmentManager , call add() on the transaction. This method receives the ID of the container for the fragment, as well as the class name of the fragment you wish to add. The added fragment is moved to the RESUMED state. It is strongly recommended that the container is a FragmentContainerView that is part of the view hierarchy.
To remove a fragment from the host, call remove() , passing in a fragment instance that was retrieved from the fragment manager through findFragmentById() or findFragmentByTag() . If the fragment’s view was previously added to a container, the view is removed from the container at this point. The removed fragment is moved to the DESTROYED state.
Use replace() to replace an existing fragment in a container with an instance of a new fragment class that you provide. Calling replace() is equivalent to calling remove() with a fragment in a container and adding a new fragment to that same container.
The following code snippet shows how you can replace one fragment with another:
Kotlin
In this example, a new instance of ExampleFragment replaces the fragment, if any, that is currently in the layout container identified by R.id.fragment_container .
By default, the changes made in a FragmentTransaction are not added to the back stack. To save those changes, you can call addToBackStack() on the FragmentTransaction . For more information, see Fragment manager.
Commit is asynchronous
Calling commit() doesn’t perform the transaction immediately. Rather, the transaction is scheduled to run on the main UI thread as soon as it is able to do so. If necessary, however, you can call commitNow() to run the fragment transaction on your UI thread immediately.
Note that commitNow is incompatible with addToBackStack . Alternatively, you can execute all pending FragmentTransactions submitted by commit() calls that have not yet run by calling executePendingTransactions() . This approach is compatible with addToBackStack .
For the vast majority of use cases, commit() is all you need.
Operation ordering is significant
The order in which you perform operations within a FragmentTransaction is significant, particularly when using setCustomAnimations() . This method applies the given animations to all fragment operations that follow it.
Kotlin
Limit the fragment’s lifecycle
FragmentTransactions can affect the lifecycle state of individual fragments added within the scope of the transaction. When creating a FragmentTransaction , setMaxLifecycle() sets a maximum state for the given fragment. For example, ViewPager2 uses setMaxLifecycle() to limit the off-screen fragments to the STARTED state.
Showing and hiding fragment’s views
Use the FragmentTransaction methods show() and hide() to show and hide the view of fragments that have been added to a container. These methods set the visibility of the fragment’s views without affecting the lifecycle of the fragment.
While you don’t need to use a fragment transaction to toggle the visibility of the views within a fragment, these methods are useful for cases where you want changes to the visibility state to be associated with transactions on the back stack.
Attaching and detaching fragments
The FragmentTransaction method detach() detaches the fragment from the UI, destroying its view hierarchy. The fragment remains in the same state ( STOPPED ) as when it is put on the back stack. This means that the fragment was removed from the UI but is still managed by the fragment manager.
The attach() method reattaches a fragment from which it was previously detached. This causes its view hierarchy to be recreated, attached to the UI, and displayed.
As a FragmentTransaction is treated as a single atomic set of operations, calls to both detach and attach on the same fragment instance in the same transaction effectively cancel each other out, thus avoiding the destruction and immediate recreation of the fragment’s UI. Use separate transactions, separated by executePendingOperations() if using commit() , if you want to detach and then immediately re-attach a fragment.
Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.
Источник