Paging your RecyclerView
Everyone knows how to display multiple pages using a
ViewPager, but since support library version 24.2.0 came out this is no longer the only way. With
SnapHelper you can easily add a pager-like feel to your RecyclerView and maybe even make your life easier in the process. This post is about how to setup your RecyclerView along with those page indicators that everyone loves. If you read some of my blog, you might already know what’s coming next:
More about ItemDecorations! :D
First things first. The setup for your RecyclerView is as easy as it gets. Just make sure that the item layouts have
layout_width="match_parent" or you will have a hard job “paging” your items. Your RecyclerView should either have a fixed height—
match_parent is also valid—or
wrap_content if you can ensure that all your items have the same height.
Just add a
PagerSnapHelper to your RecyclerView and you’re ready.
We now have a bland, paging RecyclerView—okay, I’m not a designer, and this could be made to look better—where I added a background color here so that we can draw our decorations in white at the bottom of the view.
Adding the Pager Indicator
Note: If you have no idea what decorations are you might find this introduction to decorations a better place to start where I show how to draw a simple line between items.
Next up we need to add the decoration to draw the indicator. We create a
LinePagerIndicatorDecoration and add it to our RecyclerView:
We focus on 2 methods for our decoration:
getItemOffsetsto add some padding at the bottom where we can draw the decoration without overlaying any items view
onDrawOverto draw our decoration on top of our view, which is especially important if we chose to not include an offset with
I like to use
getItemOffsets to make sure I don’t draw over any items, but if you prefer your indicator to overlay your views, you can just omit this method. All we do is request an
indicatorHeight offset at the bottom of every view. If you were to use a GridLayoutManager you need to make sure only to offset the bottom row of your items.
This offset at the bottom is also why I set a background to the RecyclerView above and not to the pages themselves. The offset reserves a space for our decoration below the content, so setting a background color on the items would have no effect since the decoration gets drawn below. If you choose not to offset your items and overlay them, you don’t need to set a background color on your RecyclerView either.
Next we make sure to draw those indicators for all of our pages. We center the indicator at the bottom of the RecyclerView and draw a simple line for every item with some padding inbetween.
This gives us the ability to draw one marker for every item, but there is no highlight yet that would indicate which page was active. In the next step we calculate how far we scrolled to smoothly animate between pages and draw the highlight.
We check with the LayoutManager to find the active page, then calculate the progress of the swipe by checking where the left side of the view is. This approach will obviously only work if your views width is
match_parent or there would be different values and undefined behavior otherwise. To improve the look and feel of this animation I use an
AccelerateDecelerateInterpolator on the progress values which results in a more natural look.
With this progress we can now draw the highlight. It displays how far the user is along their swipe between pages. We use this
progress to draw a partial highlight on the page indicators of both pages that are visible, or just a single plain highlight if the RecyclerView is at rest.
All of this gives us the promised indicator and we can now properly page our RecyclerView.
The full source code can be found at my GitHub repository.
Where to Go from Here?
As you may have noticed, I chose to draw lines instead of circles, but drawing circles and animating their alpha values would be just as easy. By using similar approaches you can do a lot of things with decorations and create reusable parts that do not require you to modify your other code.
The solution presented here is a proof of concept, and there are still a couple of sources for potential errors. As mentioned, the function to determine the progress might break with different widths, and an approach like
SnapHelper uses internally would be better fitted. Make sure to test the implementation if you choose to use it in your app!