RecyclerView.ItemDecoration: Making the Most of It

Oleg Zhilo
Surf
Published in
8 min readOct 20, 2021

Hi everyone! My name is Oleg Zhilo and for the last 5 years, I’ve been an Android developer at Surf. Throughout this time, I’ve taken part in all sorts of kick-ass projects, but I’ve had some experience with legacy code as well.

All of those projects had at least one thing in common: all of the apps had a list of elements. For instance, a list of contacts in a phone book, or a list of account settings.

To add a list to a project we use RecyclerView. I’m not going to tell you how to write an Adapter for RecyclerView, or how to properly update the listed data. In this article, I will tell you about another important component that often gets overlooked — RecyclerView.ItemDecoration. I’ll show you what it’s capable of and how to implement it when rendering a list.

Apart from listed data, RecyclerView has some crucial decorative elements, such as scroll bars and dividers between items. And that’s where RecyclerView.ItemDecoration can help us draw all of the elements without having to spawn any unnecessary Views while we render items and screens.

ItemDecoration is an abstract class with 3 methods:

Any content drawn by this method will appear underneath the ViewHolder

Any content drawn by this method will appear over the ViewHolder

A method that sets offsets to a ViewHolder in RecyclerView

If you look at signatures of onDraw* methods, you can see there are 3 main components used to draw decorations.

  • Canvas — basically, a canvas to draw any necessary decorations on.
  • RecyclerView — accesses parameters of RecyclerVIew itself.
  • RecyclerView.State — stores the state of RecyclerView.

Adding Decorations to RecyclerView

There are two methods you can use to add an ItemDecoration instance to RecyclerView:

All added instances of RecyclerView.ItemDecoration are put in a single list and drawn simultaneously.

In addition to that, there are methods you can use to manage ItemDecorations in RecyclerView.

Remove ItemDecoration at position index

Remove an instance of ItemDecoration

Get an ItemDecoration at position index

Find out the amount of ItemDecorations currently added to RecyclerView

Redraw the current list of ItemDecorations

You can already find implementations of RecyclerView.ItemDecoration, such as DividerItemDecoration, in SDK. It helps you draw dividers between items.

It’s pretty straightforward: you use a drawable and DividerItemDecoration sets it for your divider.

Let’s create a divider_drawable.xml:

and bind DividerItemDecoration to RecyclerView:

Here’s what we’ll get:

It’s perfect for simple cases.

If we look under the hood, DividerItemDecoration is pretty straightforward:

Every time onDraw(…) is called, we go through all the Views there are in RecyclerView in a cycle and draw the passed drawable.

However, the layout elements on your screen can be more complex than a list of identical elements. A screen can have:

A. Several types of items,

B. Several types of dividers,

C. Items with rounded corners,

D. Items with different vertical and horizontal margins depending on this or that condition,

E. All of the above at the same time.

Let’s pick item E — the toughest challenge — and see how we can take it on.

The Challenge:

  • We’ve got 3 types of unique items on the screen. Let’s call them A, B, and C.
  • All items have a horizontal offset of 16dp.
  • In addition to that, item B has a vertical offset of 8dp.
  • Item A has rounded corners either at the top (if it’s the first one in the group) or at the bottom (if it’s the last one in the group).
  • We need to draw dividers between items, BUT there has to be no divider after the last item.
  • Item C has to have a parallax background image.

The result should look like this:

Let’s see what options we have:

Adding different types of items to a list

You could write your own Adapter or use your favourite library.

I’ll use EasyAdapter.

Spacing the items

Here we have three options:

  1. Set paddingStart and paddingEnd for RecyclerView.
    This option won’t work if paddings are not the same for all the items.
  2. Set layout_marginStart and layout_marginEnd for each item.
    We’ll have to set the same margin for each of the items in the list.
  3. Write an implementation of ItemDecoration and override the getItemOffsets method.
    That’s more like it. The resulting solution will be more flexible and reusable.

Items with rounded corners

This one seems obvious: just add an enum {Start, Middle, End } and set it for each item together with the data. But here’s what can go wrong:

  • The data model gets more complex.
  • To pull it off, we’ll need to calculate the enum for each item in advance.
  • Every time an item is removed from or added to the list, we’ll need to do the counting again.
  • ItemDecoration. You can figure out which item of the group you’re dealing with and draw a corresponding background using the onDraw* method in ItemDecoration.

Dividers

Drawing dividers inside items is a bad practice. You’ll end up with an overcomplicated layout and, if the screen is complex, you’ll have trouble displaying the dividers dynamically. That’s why ItemDecoration wins again. A ready-made DividerItemDecoration from an SDK won’t do, because it draws dividers after every single item and there’s no way to fix it out of the box. We have to write our own implementation.

Parallax background image

What you may come up with is registering RecyclerView OnScrollListener and using a custom View to draw the image. But once again ItemDecoration comes to the rescue since it has access to the Canvas of the Recycler and all the parameters we may need.

Bottom line — we need to write at least 4 implementations of ItemDecoration. It’s pretty cool that we can simply narrow all of it down to ItemDecoration and stay out of the layout and business logic of a feature. What’s more, all the implementations of ItemDecoration can be reused, if there are any similar cases in the app.

However, over the last few years, complex lists have become more common in our practice. As a result, we had to write a new set of ItemDecoration for each project. We needed our solution to be more flexible so it could be reused in other projects.

What we strived to achieve:

  1. Write as few implementations of ItemDecoration as possible.
  2. Separate the logic of drawing on Canvas from offsets.
  3. Benefit from onDraw and onDrawOver methods.
  4. Make decorator configurations more flexible (e.g., draw dividers for specific items depending on conditions you set).
  5. Create a solution that’s not centered around dividers. After all, ItemDecoration can do so much more than draw horizontal and vertical lines.
  6. Something you can easily use taking a sample project as a reference.

That’s how we ended up with a library called RecyclerView decorator.

This library has a simple Builder interface and separate interfaces for Canvas and paddings. Moreover, it supports onDraw and onDrawOver methods. And there’s only one implementation of ItemDecoration.

Let’s get back to our challenge and see what we can do using this library.

The Builder we have in our decorator looks pretty simple:

  • .underlay(…) — draws under a ViewHolder.
  • .overlay(…) — draws over a ViewHolder.
  • .offset(…) — sets offsets for a ViewHolder.

It takes 3 interfaces to draw decorations and set offsets.

  • RecyclerViewDecor — draws decorations in RecyclerView.
  • ViewHolderDecor — draws decorations in RecyclerView while giving you access to a ViewHolder.
  • OffsetDecor — sets offsets.

But that’s not it. You can bind ViewHolderDecor and OffsetDecor to a particular ViewHolder using viewType, thus combining several types of decorations in a single list or even a single item. If you don’t pass viewType, ViewHolderDecor and OffsetDecor simply apply to all ViewHolders in RecyclerView. RecyclerViewDecor can’t do that since it’s only designed to work with RecyclerView in general and not with ViewHolders. Moreover, the same instance of ViewHolderDecor/RecyclerViewDecor can be passed to both overlay(…) and underlay(…).

Let’s get down to coding

In order to create a ViewHolder, the EasyAdapter library uses ItemControllers. In short, they create and identify ViewHolders. In our case a single controller that can display multiple distinct ViewHolders will suffice. All that matters is that each type of item layout has to have a unique viewType. This is how it looks:

In order to have offsets we need an OffsetDecor implementation:

To draw a ViewHolder with rounded corners, we need ViewHolderDecor. We’ll also need the OutlineProvider so that a pressed item can also have rounded corners.

To draw dividers we’ll add one more implementation of ViewHolderDecor:

In order to configure our divider, we’ll use a Gap.kt class:

This one will help us set the color, height, horizontal padding, and parameters of the divider.

And there’s only one ViewHolderDecor implementation left. This one will apply parallax to the background image.

Now let’s put it all together.

Let’s initialize RecyclerView and add our decorator and controllers:

Done! We’ve got all the decorations for our list.

We’ve written a set of decorators that can easily be reused and adjusted.

Let’s see where else we could apply the decorators.

PageIndicator for horizontal RecyclerView

CarouselDecoratorActivityView.kt

Message bubbles in a chat and a scroll bar

ChatActivityView.kt

A more complex case — drawing shapes and icons and changing themes without having to refresh the screen:

TimeLineActivity.kt

Sticky header

StickyHeaderDecor.kt

Source code and examples

Summary

Despite its simple interface, ItemDecoration can help you do some complex stuff to a list without having to alter the layout. So, you should definitely check it out. And make sure to try our library, you’ll find it makes it much easier to decorate a list.

Want to stay on top of app development trends and know how to create apps millions of people love?

Follow Surf on Twitter!

--

--