Part 4- Item Decorations in RecyclerView
Jan 31, 2019 · 7 min read
Hi, if you’re joining from this tutorial, you should check out my first one: Understanding RecyclerView. A high-level Insight. Part -1. Today I am attempting to explain the Item Decorations in RecyclerView in very simple way so that all the developers new to Android World can fully understand how the RecyclerView Works.
Before we begin, I would love for you to try my latest gaming application that implements all of the features that I am going to talking in this series. Any feedback is appreciated. Open this link in your mobile, if possible.
Try it: Gamezop
This has been a long journey and I have received much positive feedback on the previous articles. The fact that the articles procures information for the new and coming developers, is the reason why they have succeeded. So following the same pattern, today I am bringing this tutorial that highlights Item Decoration in Recyclerviews.
I have always felt that while learning somethi n g new, if I get to build a beautiful interfaces, why not? And as a beginner, I have always wondered how everything was built. Such beautiful things! And after much hard work, I have brought you this article that will not only help you learn about the Item Decoration but also build a beautiful app which is very similar to Starbucks Coffee Application. Okay, practically it’s a clone.
And this what we’re building today:
Questions we’re going to ask and see the answers to:
- What is Item Decoration?
- Why do we use them?
- What if we use a view or a drawable on our own instead of Item Decoration?
- Can we build something good?
Let’s try and answer those questions one by one. What we’re going to do is, we’re going to tackle each question and then code a little bit and then tackle another question and then code a little bit more to just keeps things a little more interesting. Not that I am saying that Item Decorations are boring. Okay, they’re easy. So guess what!
What are Item Decorations?
Item Decorations allow us to draw on all 4 sides of the item in an Adapter based data-set. What I mean is, when you need to add dividers or spaces or any special effects to the children of Recyclerview, you delegate your work to Item Decorations. They will beautifully handle all the work for you. Obviously you need to code a few lines. DUH.
Coding Task 1:
As I said, here’s some coding task. Head over to this GitHub link and download or fork the repo. If you don’t know how to, head over to this link first for cloning and this link for forking. (Do not forget to star the repository)
You can also start with a new repository with a clean slate.
After you’re done with this, checkout the state2 branch in the terminal in Android Studio. Though the project starts with the branch state1 but that merely creates a set up for a recyclerview. This is how you do this if you don’t know, just type in this in your terminal:
`git checkout state2` and hit enter.
I am not going to start from scratch because I have covered that out in my previous tutorials.
L et’s start with coding. Create a class named DividerItemDecoration that extends RecyclerView.ItemDecoration. We need to implement the two methods namely, getItemOffsets and onDraw.
Let’s start with onDraw.
This method runs once per drawing on the screen. I will tell you what this means later while we debug the application. But keep in mind for now.
So, item Decoration is not merely drawing dividers, we can draw anything. It helps us drawing on all 4 sides of the item view. So dividers which we thought would go just below each item, we can draw them on all four sides if you like it. But that would be one damn ugly app.
After so much waiting and setting up the project, let’s start.
For starters, were trying to draw dividers under each item. For that, declare 2 variables named anything you’d like but I would name them dividerLeft and dividerRight.
DividerLeft is the point where I want my divider to start from and DividerRight is the point where I want my divider to stop.
32 is an arbitrary integer I chose for margin. See the below image for a better uderstanding. You could even set the integer to the margin from the parent’s layout (if any).
Now that the left and right ends are done for the divider, let’s start with the top and bottom of it.
We know that recyclerview can have different type of views at different positions. We need to know them before-hand and draw dividers for them accordingly (only for the ones which are currently displayed on the screen right now).
If you don’t get the code now, just read the explanation below.
We’ll loop over every child that can be displayed over the screen and set a divider for them.
We need to know the params of the recyclerview like the margins, if any. For that we’ll get the Layout Params. Then at the bottom of the view, we want to draw a divider, so we’ll calculate the location at which we want to draw. We want to draw at the bottom of each item (so that’s why, child.getBottom()). We also want to draw after the margin we’ve set for each view while defining in the list_item.xml (and that’s why, params.bottomMargin).
Then tell the drawable that we’ve declared where you want this drawable to be and set the bounds of the drawable like in the code above. (I haven’t told you about drawable, so just assume that it’s what we want to be as our divider). And then finally call
mDivider.draw(c) (where c is canvas).
The if statement is so that the drawable in not drawn for the last child.
The onDraw method is called only for the children that are visible and for the views it keeps for recycling.
Why do we use them?
Oh, I guess I answered your question in the code task above. It’s easy and helps us with custom decorations and fast.
Источник
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
Источник