- Кастомный ItemDecoration для RecyclerView
- 1. Простой ItemDecoration : DividerItemDecoration
- 2. Custom ItemDecoration
- 2.1. Меняем ItemDecoration в зависимости позиции элемента
- 2.2. Удаляем ItemDecoration в конце списка
- 2.3. Оформление в зависимости от типа View
- RecyclerView.ItemDecoration: используем по максимуму
- Подключение к RecyclerView
- Заключение
Кастомный ItemDecoration для RecyclerView
Предисловие переводчика:
Оригинальная статья содержит GIF-анимации с демонстрацией работы кода (т. е. результат). К сожалению, мне не удалось вставить их в статью, т. к. я использовал новый редактор от Хабра, а он очень криво работает с изображениями 🙂 Для просмотра анимаций, вы можете перейти к оригинальной статье
Как-то раз мне нужно было создать собственный ItemDecoration, и я обнаружил, что в Интернете. почти нет ответов на этот вопрос. Надеюсь, что эта статья будет кому-нибудь полезна.
Простой ItemDecoration
Кастомный ItemDecoration
ItemDecoration основанный на позиции
ItemDecoration без отрисовки после последнего списка
ItemDecoration основанный на типе View
1. Простой ItemDecoration : DividerItemDecoration
Основной вариант использования может быть реализован с помощью DividerItemDecoration предоставляемый Android-ом.
Создает разделитель RecyclerView.ItemDecoration который можно использовать с LinearLayoutManager.
@param context […] будет использоваться для доступа к ресурсам.
@param orientation […] должен быть #HORIZONTAL или #VERTICAL.
Что вам нужно, так это установить правильную ориентацию, а затем предоставить drawable-ресурс, используемый для разделения каждого элемента.
Преимущества
Предоставляется самим Android
Прост в использовании
Недостатки
Как было упомянуто: ограниченные варианты использования — в качестве разделителя пустым пространством или линией.
Ограничение при использовании и «бесконечной» прокруткой (или пагинацией): декоратор будет отрисован для каждого элемента, в том числе и последнего. В большинстве случаев мы этого не хотим.
2. Custom ItemDecoration
Для лучшего контроля и более сложных вещей мы расширим RecyclerView.ItemDecoration.
Нам доступны 3 метода:
getItemOffsets используется для определения расстояния между элементами.
onDraw используется для отрисовки в пространстве между элементами.
onDrawOver то же, что и onDraw, только вызывается после того, как сам элемент отрисован.
? Cм. официальную документацию: getItemOffsets , onDraw , onDrawOver .
? Обратите внимание, что в примерах используется горизонтальная ориентация, но то же самое можно сделать и с вертикальной.
2.1. Меняем ItemDecoration в зависимости позиции элемента
Для начала простой пример: давайте украсим элементы с нечетной позицией в адаптере.
Кастомный ItemDecoration на основе позиции View
Главный метод здесь здесь parent.getChildAdapterPosition (view)
Он возвращает позицию переданной View в адаптере. См. полную документацию.
⚠️ Не путать с дочерними позициями родителей ( parent.children — это элементы, отображаемые на экране).
⚠️ Не забудьте обработать случай с RecyclerView.NO_POSITION , т. к. getChildAdapterPosition может вернуть -1.
2.2. Удаляем ItemDecoration в конце списка
Это наиболее распространенный вариант использования на StackOverflow, о котором вы спрашиваете. Это кастомный ItemDecoration , основанный на позиции элемента в адаптере.
Пользовательское оформление DividerItemDecoration, за исключением последнего элемента
Исключим последний элемент, добавив if (childAdapterPosition == adapter.itemCount-1) .
⚠️ Нужно помнить, что parent.adapter может быть null.
? Обратите внимание, что в большинстве случаев вам, вероятно, нужен только интервал между вашими элементами (за исключением последнего). Нет необходимости переопределять метод onDraw , достаточно установить правильный размер Rect в методе getItemsOffsets.
Пользовательский DividerItemDecoration, с добавлением интервала между элементами, за исключением последнего
2.3. Оформление в зависимости от типа View
В этом примере у нас есть 2 типа элементов, показанных черным и серым. Мы оформляем элементы синими и красными Drawable в зависимости от типа View (объявленных в Adapter-е ).
Пользовательский ItemDecoration в зависимости от типа View
Главная строчка кода здесь здесь — adapter.getItemViewType(childAdapterPosition) . Нам нужно переопределить метод getItemViewType в нашем адаптере.
Он возвращает тип View элемента по позиции для повторного использования View. […] Подумайте над тем, чтобы использовать id-ресурсы для уникальной идентификации типов View.
Как только вы сможете определить тип вашего элемента, вы можете установить правильный интервал и украсить его по своему желанию ?.
Вы можете найти полный код в моём GitHub репозитории.
Источник
RecyclerView.ItemDecoration: используем по максимуму
Привет, дорогой читатель Хабра. Меня зовут Олег Жило, последние 4 года я Android-разработчик в Surf. За это время я поучаствовал в разного рода крутых проектах, но и с легаси-кодом поработать довелось.
У этих проектов есть как минимум одна общая деталь: везде при разработке приложения использовался список с элементами. Например, список контактов телефонной книги или список настроек вашего профиля.
В наших проектах для списков используется RecyclerView. Я не буду рассказывать, как писать Adapter для RecyclerView или как правильно обновлять данные в списке. В своей статье расскажу о другом важном и часто игнорируемом компоненте — RecyclerView.ItemDecoration, покажу как его применить при вёрстке списка и на что он способен.
Кроме данных в списке в RecyclerView есть ещё важные элементы декора, например, разделители ячеек, полосы прокрутки. И вот тут нам поможет RecyclerView.ItemDecoration отрисовать весь декор и не плодить лишние View в вёрстке ячеек и экрана.
ItemDecoration представляет из себя абстрактный класс с 3-мя методами:
Метод для отрисовки декора до отрисовки ViewHolder
Метод для отрисовки декора после отрисовки ViewHolder
Метод для выставления отступов у ViewHolder при заполнении RecyclerView
По сигнатуре методов onDraw* видно, что для отрисовки декора используется 3 основных компонента.
- Canvas — для отрисовки необходимого декора
- RecyclerView — для доступа к параметрам самого RecyclerVIew
- RecyclerView.State — содержит информацию о состоянии RecyclerView
Подключение к RecyclerView
Для подключения экземпляра ItemDecoration к RecyclerView есть два метода:
Все подключенные экземпляры RecyclerView.ItemDecoration добавляются в один список и отрисовываются сразу все.
Также RecyclerView имеет дополнительные методы для манимуляции с ItemDecoration.
Удаление ItemDecoration по индексу
Удаление экземпляра ItemDecoration
Получить ItemDecoration по индексу
Получить текущее количество подключенных ItemDecoration в RecyclerView
Перерисовать текущий список ItemDecoration
В SDK уже есть наследники RecyclerView.ItemDecoration, например, DeviderItemDecoration. Он позволяет отрисовать разделители для ячеек.
Работает очень просто, необходимо использовать drawable и DeviderItemDecoration отрисует его в качестве разделителя ячеек.
И подключим DividerItemDeoration к RecyclerView:
Идеально подходит для простых случаев.
Под «капотом» DeviderItemDecoration всё элементарно:
На каждый вызов onDraw(. ) циклом проходим по всем текущим View в RecyclerView и отрисовываем переданный drawable.
Но экран может содержать и более сложные элементы вёрстки, чем список одинаковых элементов. На экране могут присутствовать:
а. Несколько видов ячеек;
b. Несколько видов дивайдеров;
c. Ячейки могут иметь закругленные края;
d. Ячейки могут иметь разный отступ по вертикали и горизонтали в зависимости от каких-то условий;
e. Всё вышеперечисленное сразу.
Давайте рассмотрим пункт e. Поставим себе сложную задачу и рассмотрим её решение.
- На экране есть 3 вида уникальных ячеек, назовём их a, b и с.
- Все ячейки имеют отступ в 16dp по горизонтали.
- Ячейка b имеет ещё отступ в 8dp по вертикали.
- Ячейка a имеет закруглённые края сверху, если это первая ячейка в группе и снизу, если это последняя ячейка в группе.
- Между ячейками с отрисовываются дивайдеры, НО после последней ячейки в группе дивайдера быть не должно.
- На фоне ячейки c рисуется картинка с эффектом параллакса.
Должно в итоге получиться так:
Рассмотрим варианты решения:
Заполнение списка ячейками разного типа.
Можно написать свой Adapter, а можно использовать любимую библиотеку.
Я буду использовать EasyAdapter.
Выставление отступов ячейкам.
Тут есть три способа:
- Проставить paddingStart и paddingEnd для RecyclerView.
Данное решение не подойдёт, если не у всех ячеек отступ одинаковый. - Проставить layout_marginStart и layout_marginEnd у ячейки.
Придётся всем ячейкам в списке проставлять одни и те же отступы. - Написать реализацию ItemDecoration и переопределить метод getItemOffsets.
Уже лучше, решение получится более универсальное и переиспользуемое.
Закругление углов у групп ячеек.
Решение кажется очевидным: хочется сразу добавить какой-нибудь enum
- Модель данных в списке усложняется.
- Для таких манипуляций придётся заранее просчитывать какой enum проставлять каждой ячейке.
- После удаления/добавления элемента в список придётся это пересчитывать заново.
- ItemDecoration. Понять какая это ячейка в группе и правильно отрисовать фон можно в методе onDraw* ItemDecoration’a.
Рисование дивайдеров.
Рисование дивайдеров внутри ячейки — плохая практика, так как в итоге получится усложненная вёрстка, на сложных экранах начнутся проблемы с динамическим показом дивайдеров. И поэтому ItemDecoration снова выигрывает. Готовый DeviderItemDecoration из sdk нам не подойдёт, так как отрисовывает дивайдеры после каждой ячейки, и это никак не решается из коробки. Надо писать свою реализацию.
Паралакс на фоне ячейки.
На ум может прийти идея проставить RecyclerView OnScrollListener и использовать какую-нибудь кастомную View для отрисовки картинки. Но и здесь нас снова выручит ItemDecoration, так как он имеет доступ к Canvas Recycler’а и ко всем нужным параметрам.
Итого, нам необходимо написать как минимум 4 реализации ItemDecoration. Очень хорошо, что все пункты можем свести к работе только с ItemDecoration и не трогать вёрстку и бизнес логику фичи. Плюс, все реализации ItemDecoration получится переиспользовать, если у нас есть похожие кейсы в приложении.
Однако последние несколько лет у нас в проектах всё чаще появлялись сложные списки и приходилось писать каждый раз набор ItemDecoration под нужды проекта. Было необходимо более универсальное и гибкое решение, чтобы была возможность переиспользовать на других проектах.
Каких целей хотелось добиться:
- Писать как можно меньше наследников ItemDecoration.
- Отделить логику отрисовки на Canvas и выставления отступов.
- Иметь преимущества работы с методами onDraw и onDrawOver.
- Сделать более гибкие в настройке декораторы (например, отрисовка дивайдеров по условию, а не всех ячеек).
- Сделать решение без привязки к Дивайдерам, ведь ItemDecoration способен на большее, чем рисование горизонтальных и вертикальных линий.
- Этим можно легко пользоваться, смотря на сэмпл проект.
В итоге у нас получилась библиотека RecyclerView decorator.
Библиотека имеет простой Builder интерфейс, отдельные интерфейсы для работы с Canvas и отступами, также возможность работать с методами onDraw и onDrawOver. Реализация ItemDecoration всего одна.
Давайте вернёмся к нашей задаче и посмотрим, как её решить с помощью библиотеки.
Builder нашего декоратора выглядит просто:
- .underlay(. ) — нужен для отрисовки под ViewHolder.
- .overlay(. ) — нужен для отрисовки над ViewHolder.
- .offset(. ) — используется для выставления отступа ViewHolder.
Для отрисовки декора и выставления отступов используется 3 интерфейса.
- RecyclerViewDecor — отрисовывает декор на RecyclerView.
- ViewHolderDecor — отрисовывает декор на RecyclerView, но даёт доступ к ViewHolder.
- OffsetDecor — используется для выставления отступов.
Но это не всё. ViewHolderDecor и OffsetDecor можно привязать к конкретному ViewHolder с помощью viewType, что позволяет комбинировать несколько видов декоров на одном списке и даже ячейке. Если viewType не передавать, то ViewHolderDecor и OffsetDecor будут применяться ко всем ViewHolder в RecyclerView. RecyclerViewDecor такой возможности не имеет, так как рассчитан на работу с RecyclerView в общем, а не с ViewHolder’ами. Плюс один и тот же экземпляр ViewHolderDecor/RecyclerViewDecor можно передавать как в overlay(. ) так underlay(. ).
Приступим к написанию кода
В библиотеке EasyAdapter для создания ViewHolder используются ItemController’ы. Если коротко, они отвечают за создание и идентификацию ViewHolder. Для нашего примера хватит одного контроллера, который может отображать разные ViewHolder. Главное, чтобы viewType был уникальный для каждой вёрстки ячейки. Выглядит это следующим образом:
Для выставления отступов нам нужен наследник OffsetDecor:
Для отрисовки закруглённых углов у ViewHolder нужен наследник ViewHolderDecor. Тут нам понадобится OutlineProvider, чтобы press-state тоже обрезался по краям.
Для рисования дивайдеров напишем ещё одного наследника ViewHolderDecor:
Для настройки нашего дивадера будем использовать класс Gap.kt:
Он поможет настроить цвет, высоту, горизонтальные отступы и правила отрисовки дивайдера
Остался последний наследник ViewHolderDecor. Для рисования картинки эффектом параллакса.
Соберём теперь всё вместе.
Инициализируем RecyclerView, добавим ему наш декоратор и контроллеры:
На этом всё. Декор нашего списка готов.
У нас получилось написать набор декораторов, которые можно легко переиспользовать и гибко настраивать.
Посмотрим как ещё можно применить декораторы.
PageIndicator для горизонтального RecyclerView
ChatActivityView.kt |
TimeLineActivity.kt |
Заключение
Несмотря на простоту интерфейса ItemDecoration, он позволяет делать сложные вещи со списком без изменения вёрстки. Надеюсь мне удалось показать, что это достаточно мощный инструмент и он достоин вашего внимания. А наша библиотека поможет вам декорировать списки проще.
Всем большое спасибо за внимание, буду рад вашим комментариям.
UPD: 06.08.2020 добавлен пример для Sticky header
Источник