- AKiniyalocts / EdgeDecorator.java
- RecyclerView.ItemDecoration: используем по максимуму
- Подключение к RecyclerView
- Заключение
- Кастомный ItemDecoration для RecyclerView
- 1. Простой ItemDecoration : DividerItemDecoration
- 2. Custom ItemDecoration
- 2.1. Меняем ItemDecoration в зависимости позиции элемента
- 2.2. Удаляем ItemDecoration в конце списка
- 2.3. Оформление в зависимости от типа View
AKiniyalocts / EdgeDecorator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
package com.batterii.mobile.ui.component.decorators ; |
import android.graphics.Rect ; |
import android.support.v7.widget.RecyclerView ; |
import android.view.View ; |
/** |
* Created by anthonykiniyalocts on 12/8/16. |
* |
* Quick way to add padding to first and last item in recyclerview via decorators |
*/ |
public class EdgeDecorator extends RecyclerView . ItemDecoration < |
private final int edgePadding; |
/** |
* EdgeDecorator |
* @param edgePadding padding set on the left side of the first item and the right side of the last item |
*/ |
public EdgeDecorator ( int edgePadding ) < |
this . edgePadding = edgePadding; |
> |
@Override |
public void getItemOffsets ( Rect outRect , View view , RecyclerView parent , RecyclerView . State state ) < |
super . getItemOffsets(outRect, view, parent, state); |
int itemCount = state . getItemCount(); |
final int itemPosition = parent . getChildAdapterPosition(view); |
// no position, leave it alone |
if (itemPosition == RecyclerView . NO_POSITION ) < |
return ; |
> |
// first item |
if (itemPosition == 0 ) < |
outRect . set(edgePadding, view . getPaddingTop(), view . getPaddingRight(), view . getPaddingBottom()); |
> |
// last item |
else if (itemCount > 0 && itemPosition == itemCount — 1 ) < |
outRect . set(view . getPaddingLeft(), view . getPaddingTop(), edgePadding, view . getPaddingBottom()); |
> |
// every other item |
else < |
outRect . set(view . getPaddingLeft(), view . getPaddingTop(), view . getPaddingRight(), view . getPaddingBottom()); |
> |
> |
> |
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Источник
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
Источник
Кастомный 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 репозитории.
Источник