Это третья часть цикла статей про разработку адаптеров для 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 } } )
Если элемент «премиум», он будет занимать две ячейки, в противном случае одну. Результат:

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