Пример использования: отображение верхнего и нижнего колонтитулов списка.

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 изменяется, мы уведомляем, что элемент должен быть изменен, вставлен или удален.

? Подробнее о MergeAdapter

ViewHolders

По умолчанию каждый адаптер поддерживает свой собственный пул ViewHolder, без повторного использования между адаптерами. Если несколько адаптеров отображают один и тот же ViewHolder, мы вероятно захотим повторно использовать экземпляры между ними. Мы можем достичь этого, построив MergeAdapter с помощью MergeAdapter.Config, где isolateViewTypes = false. Таким образом, все объединенные адаптеры будут использовать один и тот же пул представлений. В примере с колонтитулами состояния загрузки оба ViewHolders фактически отображают одно и то же содержимое, поэтому мы можем использовать их повторно.

⚠️ Для поддержки различных типов ViewHolder необходимо реализовать Adapter.getItemViewType. При повторном использовании ViewHolder убедитесь, что один и тот же тип представления не указывает на разные ViewHolder! Одним из лучших способов для этого является возврат идентификатора макета в качестве типа представления.

Применение постоянных ID

Вместо применения постоянных 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

Возможно, вы использовали ViewHolder.getAdapterPosition в прошлом, чтобы получить положение ViewHolder в адаптере. Теперь, поскольку мы объединяем несколько адаптеров, используйте ViewHolder.getBindingAdapterPosition(). Если вы хотите получить адаптер, который в последний раз связал ViewHolder, в случае, когда вы делитесь ViewHolder, используйте ViewHolder.getBindingAdapter().

Вот и все! Если вы хотите последовательно показывать различные типы данных, которые выиграли бы от инкапсуляции в их собственных адаптерах, начните использовать MergeAdapter. Для расширенного управления пулом ViewHolder и постоянными ID используйте MergeAdapter.Config.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Florina Muntenescu: Merge adapters sequentially with MergeAdapter

Предыдущая статьяКак спроектировать REST API для выполнения системных команд с помощью Actix Rust
Следующая статьяНе учите машинное обучение