Пример использования: отображение верхнего и нижнего колонтитулов списка.
MergeAdapter
— это новый класс, доступный в recyclerview:1.2.0-alpha02
, который позволяет последовательно объединять несколько адаптеров
для отображения в одном RecyclerView
. Это позволяет лучше инкапсулировать адаптеры, сохраняя их сфокусированными и переиспользуемыми, а не объединять множество источников данных в один адаптер.
Один из вариантов использования этого метода — отображение состояния загрузки списка в верхнем или нижнем колонтитуле: когда список извлекает данные из сети, мы хотим показать счетчик прогресса; в случае ошибки мы хотим показать ошибку и кнопку повтора.
RecyclerView с нижним колонтитулом, отображающим состояние загрузки: прогресс или ошибкаMergeAdapter
MergeAdapter
позволяет отображать содержимое нескольких адаптеров в определенной последовательности. Например, предположим, что у нас есть следующие три адаптера:
val firstAdapter: FirstAdapter = …
val secondAdapter: SecondAdapter = …
val thirdAdapter: ThirdAdapter = …
val mergeAdapter = MergeAdapter(firstAdapter, secondAdapter,
thirdAdapter)recyclerView.adapter = mergeAdapter
recyclerView
будет отображать элементы из каждого адаптера последовательно.
Наличие разных адаптеров позволяет лучше отделять задачи каждой последовательной части списка. Например, если вы хотите отобразить заголовок, вам не нужно помещать логику, связанную с отображением заголовка, в тот адаптер, который обрабатывает отображение списка. Можно просто инкапсулировать ее в свой собственный адаптер.
RecyclerView и данные AdapterНаш верхний / нижний колонтитул отображает либо индикатор прогресса, либо сообщает об ошибке. Когда список успешно завершит загрузку, верхний и нижний колонтитулы не должны ничего отображать. Поэтому они могут быть представлены в виде списка с 0 или 1 элементом, со своим собственным адаптером:
val mergeAdapter = MergeAdapter(headerAdapter, listAdapter, footerAdapter) recyclerView.adapter = mergeAdapterЕсли и верхний, и нижний колонтитулы используют один и тот же макет, ViewHolder
и логику UI (например, когда отображается прогресс и каким образом), вы можете реализовать только один класс Adapter
и создать два его экземпляра: один для верхнего колонтитула и один для нижнего.
Для полной реализации проверьте этот pull-реквест, который добавляет:
• LoadState
, выставленный из ViewModel
;
• Макет верхнего и нижнего колонтитулов состояния загрузки;
• Объект ViewHolder
для верхнего и нижнего колонтитулов;
• ListAdapter
, который отображает 0 или 1 элемент на основе LoadState
. Каждый раз, когда LoadState
изменяется, мы уведомляем, что элемент должен быть изменен, вставлен или удален.
По умолчанию каждый адаптер поддерживает свой собственный пул ViewHolder
, без повторного использования между адаптерами. Если несколько адаптеров отображают один и тот же ViewHolder
, мы вероятно захотим повторно использовать экземпляры между ними. Мы можем достичь этого, построив MergeAdapter
с помощью MergeAdapter.Config
, где isolateViewTypes = false
. Таким образом, все объединенные адаптеры будут использовать один и тот же пул представлений. В примере с колонтитулами состояния загрузки оба ViewHolders
фактически отображают одно и то же содержимое, поэтому мы можем использовать их повторно.
⚠️ Для поддержки различных типов ViewHolder
необходимо реализовать Adapter.getItemViewType
. При повторном использовании ViewHolder
убедитесь, что один и тот же тип представления не указывает на разные ViewHolder
! Одним из лучших способов для этого является возврат идентификатора макета в качестве типа представления.
Вместо применения постоянных ID вместе с notifyDataSetChanged
, рекомендуется использовать конкретные события уведомления адаптера, которые дают RecyclerView
дополнительную информацию об изменениях в наборе данных. Это позволяет RecyclerView
обновлять UI более эффективно и с лучшей анимацией. Если вы используете ListAdapter
, то уведомления обрабатываются за вас, под капотом, с помощью обратного вызова DiffUtil
. Но если вам действительно нужно применять постоянные ID, то MergeAdapter.Config
предоставляет для них три различные конфигурации: NO_STABLE_IDS
, ISOLATED_STABLE_IDS
и SHARED_STABLE_IDS
. Последние два требуют, чтобы вы обрабатывали их в своем адаптере. Дополнительную информацию о том, как они работают, смотрите в документации StableIdMode
.
Когда адаптерная часть MergeAdapter
вызывает одну из функций уведомлений, MergeAdapter
вычисляет новые позиции элементов перед обновлением RecyclerView
.
С точки зрения RecyclerView
, notifyItemRangeChanged
означает, что элементы одинаковы, изменилось только их содержимое. notifyDataSetChanged
означает, что между “до” и “после” нет никакой связи. Следовательно мы не можем сопоставить notifyDataSetChanged
с notifyItemRangeChanged
.
Если адаптер вызывает Adapter.notifyDataSetChanged
, MergeAdapter
также вызоветAdapter.notifyDataSetChanged
, а не Adapter.notifyItemRangeChanged
. Как обычно с RecyclerView
, избегайте вызова Adapter.notifyDataSetChanged()
, предпочитайте более детальные обновления или используйте реализацию Adapter
, которая делает это автоматически, например ListAdapter
или SortedList
.
Возможно, вы использовали ViewHolder.getAdapterPosition
в прошлом, чтобы получить положение ViewHolder
в адаптере. Теперь, поскольку мы объединяем несколько адаптеров, используйте ViewHolder.getBindingAdapterPosition()
. Если вы хотите получить адаптер, который в последний раз связал ViewHolder
, в случае, когда вы делитесь ViewHolder
, используйте ViewHolder.getBindingAdapter()
.
Вот и все! Если вы хотите последовательно показывать различные типы данных, которые выиграли бы от инкапсуляции в их собственных адаптерах, начните использовать MergeAdapter
. Для расширенного управления пулом ViewHolder
и постоянными ID используйте MergeAdapter.Config
.
Перевод статьи Florina Muntenescu: Merge adapters sequentially with MergeAdapter
Комментарии