Различные типы 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