Я действующий разработчик приложений под платформу Android. Хочу поделиться крутой библиотекой, облегчающей разработку адаптеров для RecyclerView, и описать ее использование. RecyclerView – это View элемент в Android для отображения списков, и редкое современное приложение обходится без него. Стоковая реализация адаптеров и вьюхолдеров очень громоздкая и пугающая, особенно для новичков. Благо существует библиотека BaseRecyclerViewAdapterHelper облегчающая разработку этих компонентов. В 100% проектов, которые я разрабатывал – я подключал её, и все коллеги достойно оценивали это деяние.
Часть 1. Текущая
Часть 3. MultiItem адаптер для RecyclerView в 40 строк с BRVAH
Цель BaseRecyclerViewAdapterHelper – упростить работу с отображением списков в Android. Чтобы понять, как можно облегчить работу с RecyclerView, рассмотрим базовые потребности отображения списков и базовые потребности элементов списка
Базовые потребности отображения списков в андроид:
Отобразить список
Иметь возможность взаимодействия с элементами списка
Базовые потребности элемента списка:
Отображать текст
Отображать изображения
Возможность менять свое состояние
За исполнение потребностей элемента – отвечает ViewHolder. В данной библиотеке есть реализация BaseViewHolder, им по умолчанию типизирован BaseAdapter. Это позволяет не описывать свой ViewHolder, а пользоваться базовыми методами:
Установка текста из данных
Установка видимости View
Установка изображения из ресурса
И много других, не рассмотренных в данной статье
Для наглядности я напишу простенькое приложение, которое будет отображать список уведомлений на отдельном экране. Проект будет опубликован на GitHub, ссылка в конце статьи.
Первым делом создам DataClass, который описывает элемент списка с точки зрения данных:
data class NotificationDTO( val date: String, val text: String, var isRead: Boolean = false )
date - хранит в себе дату уведомления в строковом виде
text - текст уведомления
isRead – статус уведомления, то есть было ли оно прочитано или нет.
Далее создам layout для элемента списка:

Тут расположено два TextView, для отображения даты уведомления и текста уведомления, ImageView для отображения статуса уведомления, и изменения этого статуса по нажатию. Так же есть скрытый разделитель, который будет отображаться для всех элементов, кроме нулевого.
Далее создам адаптер для отображения данных:
class NotificationAdapter(data: MutableList<NotificationDTO>) : BaseQuickAdapter<NotificationDTO, BaseViewHolder>(R.layout.item_notification, data) { init { addChildClickViewIds(R.id.ivState) } override fun convert(holder: BaseViewHolder, item: NotificationDTO) { holder.setGone(R.id.view, holder.layoutPosition == 0) holder.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 ) } }
Рассмотрим этот адаптер. Он наследуется от BaseQuickAdapter и типизируется двумя параметрами. Первый – это элемент, который нужно отобразить и BaseViewHolder. В конструктор адаптера передается список элементов для отображения, а конструктор BaseQuickAdapter передается id layout элемента списка.
При инициализации адаптера, в методе init, указываю id элементов, для которых нам нужны слушатели нажатий, в нашем случае это только ImageView, нажатиями будем менять статус элемента. Заполнение элемента данными происходит в методе convert. В нем есть дос��уп к ViewHolder и элементу списка. Методы, используемые для отображения данных:
setGone – устанавливает видимость элемента. В данном примере есть разделитель. Его необходимо делать видимым для всех элементов, кроме нулевого. Для получения порядкового номера элемента использую метод holder.layoutPosition . В метод передаю id элемента и Boolean значение, скрывать или отображать элемент
setText – устанавливает текст на TextView и его наследников. Принимает в себя id элемента и необходимый текст. В данном случае, получаем его из объекта данных
setImageResource – устанавливает изображение из ресурсов в ImageView
Чтобы отобразить данные – проинициализирую адаптер в activity, свяжу его с RecyclerView и заполню данными:
class MainActivity : AppCompatActivity() { private lateinit var rv: RecyclerView private val adapter = NotificationAdapter(mutableListOf()) private val repository = Repository() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) rv = findViewById(R.id.rvNotification) rv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) initAdapter() } private fun initAdapter() { rv.adapter = adapter val data = repository.getAll() adapter.setNewInstance(data) } }
В начале файла создал адаптер с пустым списком. Дальнейшая инициализация проходит в методе initAdapter. Тут указал, что созданный адаптер – это адаптер для нашего RecyclerView. Далее получил данные для отображения, и установил их для адаптера.
В результате отобразил весь список, пока без обработки нажатий. Добавлю обработчик.
class MainActivity : AppCompatActivity() { private lateinit var rv: RecyclerView private val adapter = NotificationAdapter(mutableListOf()) private val repository = Repository() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) rv = findViewById(R.id.rvNotification) rv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) initAdapter() } private fun initAdapter() { rv.adapter = adapter adapter.setOnItemChildClickListener { _, view, position -> if (view.id == R.id.ivState) { val item = adapter.getItem(position) if (!item.isRead) { item.isRead = true adapter.notifyItemChanged(position) } else { Toast.makeText(this, "Элемент будет удален, реализация в следющей части", Toast.LENGTH_SHORT).show() } } } val data = repository.getAll() adapter.setNewInstance(data) } }
В initAdapter, установил слушатель нажатий методом setOnItemChildClickListener. ВАЖНО у этого адаптера есть похожий метод setOnItemClickListener, но его принципиальное отличие в том, что он обрабатывает нажатия на элемент в целом, и не будет обрабатывать нажатия на «дочерние» элементы. В слушателе, проверяю на какой элемент было нажатие, в текущем примере один такой элемент, но их может быть сколько угодно. Далее проверяю прочитано это уведомление или нет. Для получения этой информации, в слушатель передается параметр «position», да получения элемента по его позиции, использую метод адаптера getItem(position). Если элемент не прочитан, то меняю ему статус и сообщаю адаптеру о том, что элемент был изменен методом notifyItemChanged(position)
Результат:

Получился читабельный адаптер в тридцать строк, покрывающий более 90% потребностей в отображении списков на Android. Другие возможности библиотеки буду рассмотрены в следующих частях это:
Установка изображение в список из сети
Анимация появления элементов
Подгрузка списка(пагинация)
Отображение загрузки списка и ошибки загрузки списка
Удаление элементов
Обработка «долгих» нажатий
Удаление элемента «свайпом»
Перемещение элементов
Использование нескольких layout в одном списке
Возможно, придумаю еще интересную тему и добавлю ее в список
Проект на Гите