MultiItem адаптер для RecyclerView в 40 строк кода с BRVAH

от автора

Это третья часть цикла статей про разработку адаптеров для RecyclerView c BRVAH.

Прошлые части:

В этой части рассмотрю:

  • Анимацию появления элементов

  • Использование нескольких layout в одном списке

Один из частых запросов бизнеса в целом и дизайна в частности – добавить анимацию появления элементов в списке. В этой библиотеке предусмотрено пять готовых анимаций

  • AlphaInAnimation() – появление элемента из невидимости

  • ScaleInAnimation() – появление элемента с его увеличением

  • SlideInBottomAnimation() – появление элемента снизу

  • SlideInLeftAnimation() – появление элемента слева

  • SlideInRightAnimation() – появление элемента справа

Есть возможность создать свою анимацию унаследовавшись от BaseAnimation

Добавление анимации происходит в одну строку кода

adapter.adapterAnimation = SlideInRightAnimation()

Результат:

Дальше рассмотрю потребность в использовании разных layout для одного списка, это может использоваться для:

  • Секционирования списка подзаголовками

  • Выделения элемента, например «премиум» объявления, на доске объявлений

Рассмотрю первую потребность. Layout, для вывода заголовка содержит в себе только TextView, для отображения текста:

Создал новый адаптер. Для секционированного списка, адаптер наследуется от BaseSectionQuickAdapter. При инициализации, основное отличие в том, что в его конструктор необходимо еще передать layout заголовка. Еще значимое отличие, при типизации адаптера, DataClass элемента реализовывает интерфейс SectionEntity, про него расскажу позже. При реализации адаптера необходимо переопределить еще метод convertHeader. В нем происходит заполнение данными layout заголовка.

class SectionAdapter(data: MutableList<NotificationWithSectionsDTO>) : BaseSectionQuickAdapter<NotificationWithSectionsDTO, BaseViewHolder>(R.layout.item_section_header, R.layout.item_notification_with_image, data) {      init {         addChildClickViewIds(R.id.ivState)     }      override fun convert(holder: BaseViewHolder, item: NotificationWithSectionsDTO) {         holder.setGone(R.id.view, holder.layoutPosition == 0)             .setText(R.id.tvDateTime, item.date)             .setText(R.id.tvDsc, item.text)             .setImageResource(                 R.id.ivState,                 if (item.isRead) R.drawable.ic_delete                 else R.drawable.ic_read             )          val imageView = holder.getView<ImageView>(R.id.imageView)         val context = holder.itemView.context          Glide.with(context)             .load(item.imageUrl)             .circleCrop()             .into(imageView)     }      override fun convertHeader(helper: BaseViewHolder, item: NotificationWithSectionsDTO) {         helper.setText(R.id.tvHeader, item.titleText)     } } 

Перейду к DataClassу. Он реализует интерфейс SectionEntity. В нем единственный метод isHeader — определяет является ли элемент заголовком. Опишу новый DataClass

data class NotificationWithSectionsDTO(     val date: String = "",     val text: String = "",     var isRead: Boolean = false,     val imageUrl: String = "",     val titleText: String = "", ) : SectionEntity {     override val isHeader: Boolean         get() = titleText.isNotBlank() } 

Инициализация адаптера не отличается от инициализации в прошлых статьях

Результат:

Секционирование – это частный случай использования списка с различными элементами, ограниченное двумя layout. Рассмотрю более обширный случай использования нескольких элементов.

Для этого, сделаю подобие доски объявлений, с обычными и «премиум» объявлениями.

Начну с DataClass. Для реализации мультиэлементности класс данные реализовывает интерфейс MultiItemEntity. В нем необходимо переопределить один метод itemType: Int, возвращающий тип элемента.

data class MessageDTO(     val text: String,     val price: String,     val image: String,     val mayAgreement: Boolean = false,     val type: Int = typeNotPremium ) : MultiItemEntity {     companion object {         const val typePremium = 0         const val typeNotPremium = 1     }      override val itemType: Int         get() = this.type } 

Создал два типа объявлений «премиум» и «не премиум», itemType возвращает их.

Создал адаптер. В этом случае, адаптер наследуется от BaseMultiItemQuickAdapter. Ему в конструктор не передаются layoutы, они инициализируются методом addItemType. Метод принимает параметры itemType: Int и layoutId: Int. Остальное аналогично BaseQuickAdapter, за исключением разбиения на типы. Для определения типа элемента, в методе convret, использую метод холдера holder.itemViewType. В зависимости от типа элемента – проставляю данные.

class MultiItemAdapter(data: MutableList<MessageDTO>?) : BaseMultiItemQuickAdapter<MessageDTO, BaseViewHolder>(data) {     init {         addItemType(MessageDTO.typeNotPremium, R.layout.item_message)         addItemType(MessageDTO.typePremium, R.layout.item_mesage_premium)     }      override fun convert(holder: BaseViewHolder, item: MessageDTO) {         val imageView = holder.getView<ImageView>(R.id.ivMsg)         val context = holder.itemView.context          Glide.with(context)             .load(item.image)             .into(imageView)          when (holder.itemViewType) {             MessageDTO.typePremium -> {                 holder.setText(R.id.tvNamePrem, item.text)                     .setText(R.id.tvPrice, item.price)                     .setGone(R.id.ivAgreement, !item.mayAgreement)                     .setGone(R.id.tvAgreement, !item.mayAgreement)             }             MessageDTO.typeNotPremium -> {                 holder.setText(R.id.tvName, item.text)                     .setText(R.id.tvPrice, item.price)                     .setGone(R.id.ivAgreement, !item.mayAgreement)                     .setGone(R.id.tvAgreement, !item.mayAgreement)             }         }     } } 

Результат:

И в завершение рассмотрю, как разместить «премиум» элемент на 2 ячейки. Делается это при инициализации адаптера. Методом setGridSpanSizeLookup адаптера, устанавливаю реализацию интерфейса GridSpanSizeLookup

adapter.setGridSpanSizeLookup(     GridSpanSizeLookup { gridLayoutManager, viewType, position ->         when (viewType) {             MessageDTO.typePremium -> 2             else -> 1         }     } ) 

Если элемент «премиум», он будет занимать две ячейки, в противном случае одну. Результат:

В этой части рассмотрел достаточно сложное требование к отображению списков, и легкую его реализацию при помощи библиотеки. В следующих частях рассмотрю:

  • Отображение загрузки списка и ошибки загрузки списка

  • Обработку «долгих» нажатий

  • Удаление элемента «свайпом»

  • Перемещение элементов

Проект на Гите

Библиотека


ссылка на оригинал статьи https://habr.com/ru/articles/578416/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *