Я действующий разработчик приложений под платформу 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 в одном списке
Возможно, придумаю еще интересную тему и добавлю ее в список
Проект на Гите
Комментарии (5)
AlexSkvortsov
06.09.2021 12:55+1Есть несколько различных реализаций, которые доведены до ума в большей степени, чем представленная.
Советую изучить хотя бы наиболее известный вариант из такихhttps://github.com/sockeqwe/AdapterDelegates
П.с. а еще библиотека из поста кажись заброшена
bsod_keks Автор
06.09.2021 15:57P.S била из статьи была обновлена 6мес назад, а Ваша 17мес назад. так что по поводу заброшенности не уверен
AlexSkvortsov
06.09.2021 17:03+1Я главным образом обращал внимание на issues и pull requests, их очень много у либы из статьи.
Ну и аккаунт сам по себе активный у адаптер-делегатов, там почти каждый месяц куда-нибудь контрибутится что-то, а у либы из статьи - ни разу за год.
В любом случае, спасибо за статью)
elzahaggard13
Очень интересный подход. Есть у меня вопросы по читаемости методов setGone/text/resource, но на вкус и цвет...