Упрощаю разработку адаптеров для RecyclerView c BRVAH. Часть 1

Kate

Administrator
Команда форума
Я действующий разработчик приложений под платформу Android. Хочу поделиться крутой библиотекой, облегчающей разработку адаптеров для RecyclerView, и описать ее использование. RecyclerView – это View элемент в Android для отображения списков, и редкое современное приложение обходится без него. Стоковая реализация адаптеров и вьюхолдеров очень громоздкая и пугающая, особенно для новичков. Благо существует библиотека BaseRecyclerViewAdapterHelper облегчающая разработку этих компонентов. В 100% проектов, которые я разрабатывал – я подключал её, и все коллеги достойно оценивали это деяние.

Цель 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 для элемента списка:

62b20784b0fb89e8f0940b8aab544fc3.png

Тут расположено два 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)

Результат:

422b97588b0c57ff69780c76b82c4efc.gif

Получился читабельный адаптер в тридцать строк, покрывающий более 90% потребностей в отображении списков на Android. Другие возможности библиотеки буду рассмотрены в следующих частях это:

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

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

 
Сверху