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

от автора

Я действующий разработчик приложений под платформу 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 для элемента списка:

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

Возможно, придумаю еще интересную тему и добавлю ее в список

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

Библиотека


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


Комментарии

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

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