Различные типы Item View в RecyclerView

Теги: Java, Android, RecyclerView, Refactoring

Заметка основана на статье Mateusz Dziubek “Multiple row types in RecyclerView”. Приводится пример создания RecyclerView c различными типами View. В процессе написания код подвергается рефакторингу. Показывается эффективность применения различных паттернов программирования и рефакторинга в целом на простом для понимания примере.

Проект на github: https://github.com/ziginsider/MultipleRowTypesInRecyclerViewDemo


Постановка задачи

Итак, представим ситуацию, когда нам надо в один RecyclerView поместить несколько различных типов View. Допустим, это:

  • кнопка
  • картинка с текстом
  • заголовок с текстом

На смартфоне мы должны получить что-то вроде этого:

Вдохновляясь статьей Replace Conditional with Polymorphism, попробуем сделать это. Основная идея - каждому типу View соответсвует отдельный класс, и эти классы объединяются общим интерфейсом.


layout

Создаем, три layout, соответствующих трем типам View.

Например, для row_type_button.xml примерно так:
Для row_type_image.xm и row_type_text.xml аналогично. Или см. по ссылкам.

layout activity_main.xml см. по ссылке.


Классы для каждого типа View

Имея в виду row_type_button, создаем для него класс. Не забываем о конструкторе и обработчике нажатия:
Как видим, RowType и будет нашим объединяющим интерфейсом.

Далее, переходим к row_type_image. Незабываем о геттере для текста:
Наконец, row_type_text. Тут добавим геттеры для заголовка и текста:

Объединяющий интерфейс

В адаптере мы будем различать типы наших View посредством int getItemViewType(int position). Теперь вспомним об особенности интерфейсов, в силу уникальности, хранить в себе константы. Т.е. сигнатура int в интерфейсе соответсвует сигнатуре public static final int в классе наследующем интерфейс.

Учитывая данную особенность, сконструируем наш интерфейс для различения типов View:


Теперь в адаптере мы сможем различать типы View используя instanceof и, таким образом, возвращать тип View. Итак…


Адаптер

Получаем примерно такой адаптер:
Что сказать? Невооруженным взглядом можно видеть что кода много. Лично у меня возникает ощущение захламленности. Давайте улучшать ситуацию.

NB: Однако же все работает. Можно запустить и проверить. Реализацию MainActivity можно глянуть на github тут. Рефакторинг мы делаем исключительно из-за архитектурных соображений. В конечном итоге такой подход приносит пользу. Да и на код не страшно смотреть.


Рефакторинг

Во-первых, у нас три различных ViewHolder. Ничто не мешает нам применить к этой тройке шаблон проектирования “Фабрика”. Одной ответственностью станет меньше.

Заводим отдельный класс ViewHolderFactory и выносим туда наши холдеры. Внутри класса определяем функцию viewHolder create(parent, viewType), которая будет создавать необходимый холдер, согласно типу View:
Кроме того каждый data-класc, представляющий определенный тип View (ButtonRowType, ImageRowType, TextRowType), сам может возвращать тип своего View и самостоятельно Bind’ить ViewHolder. Для этого добавляем в интерфейс RowType две фунции:

Ниже пример реализации этих функций для класса TextRowType. Обратите внимание, что теперь нет необходимости в геттерах.

Полностью классы после рефакторинга см. github тут


Аналогично реализуем методы для остальных классов. В итоге получаем такой адаптер:
Кода в адаптере в три раза меньше. Код понятнее. Масштабировать/изменять проект гораздо легче.

А что еще надо?


Bonus

Допольнительно поговорим о более тонкой настройке RecyclerView.

RecyclerView с разными типами Item View, для того, чтобы избежать притормаживания при прокрутке, можно настроть чуть эффективнее.

Только сразу оговорим откуда может взяться притормаживание: в тех случаях когда в пуле RecyclerView (куда уходят ViewHolder после попадания за экран для последующего переиспользования) банально не будет места. Это может случится, например, при crossfade анимации всего списка элементов.

Дело в том, что по-умолчанию количество объектов конкретного View попадающих в пул RecyclerView равно 5. Причем все типы View у RecyclerView используют один пул объектов. Таким образом, зная что одни типы View появляются в нашем списке чаще, а другие реже, мы можем увеличить максимальное количество первых в пуле и уменьшить максимальное количество вторых. Как это сделать?

Это важно знать. Т.к. допустим, элемент списка конкретного View, который как мы знаем на экране всегда представлен в одном экземпляре, может забивать пул объектов RecyclerView до пяти и, таким образом, четыре из этих ViewHolder будут напрасно хранится в пуле, занимая место, которое могли бы занимать элементы с типом View, которого на экране может быть больше.

Теперь все.

Проект на github: https://github.com/ziginsider/MultipleRowTypesInRecyclerViewDemo

13 11 2017

Теги заметки: