Implementing Sticky RecyclerView Grid Headers on Android

Hello guys!

I am working on an application that needs to display certain amount of items in a a scrollable view that have headers on top of every one of them, so as the title says, RecyclerView.

However, my data that is coming from SQLite backed database didn’t fit too well on the mould of just using different kind of view types, because the data was coming from SQLite on demand and if I were to use view types to support headers I would have to process the whole database before showing it and add some kind of layer that provides me with the headers.

After couple of quick googles I found couple of promising libraries , but unfortunately for my need, they didn’t have support for GridLayout or required same kind of view type mapping as regular headers, but I wouldn’t give up now, as I knew RecyclerView provides lovely way of adding custom decorations, which were used to implement timeshops sticky headers, and after studying the code of other implementations I was ready to go!

But first I needed to make my own SpanSizeLookUp implementation to separate different sections of the grid.

Sectioned SpanSizeLookUp sketch
Sectioned SpanSizeLookUp sketch

So the basic idea is that last item of each section is given with a span that makes it fill the rest of the row, so that the next sections items are dropped to the next row.

First I needed the information of which item belongs to which section, which I made my StickyGridHeadersAdapter in similar fashion as in timeshops implementation.

So the primary function of the adapter is to provide the header id for each position and its corresponding view.

So now that I got the information I am ready to extend the SpanSizeLookUp!

SpanSizeLookUp has one important function of determining each items span size, which is done by implementing getSpanSize method that takes the position of them item as parameter.

My implementation of getSpanSize first determines whether the item in question is the last item before next header, and if so, it calculates the remaining amount of columns in that row, that it needs to fill in order to bump the following item to next row.

So first we implement the last item before next header check, which is quite simple, take current and next item, and if their ids are different, they belong to different categories, meaning that the item in question is actually the last item before next header! Pretty simple huh?

Then we need to implement the actual span size lookup, it needs a little bit more logic!

So first we use our lastly implemented method and check if it actually is the last item within the section, and if not, just return one.

But if the case is different, aka if it is the last item, we first calculate the offset of this item in question within the section, which is needed to calculate the remaining columns within the row, but for that we need to implement a way to calculate those preceding items.

Fortunately for us, its quite simple chore, given the fact that we can query the header id of each item, so basically what we do is we loop through each item preceding the current item til we find a change in header id!

And to account for the case that this happens to be within the first category, we just return the given position, as it is the offset to the start of the list.

So with that info we can now calculate the column index..

which is just taking modulus of the offset within category and the span count

and to get the amount of items there are left on this row after the particular item, we just substract the column index + 1 from the span count, accounting for the the range of 0.. span count – 1 of the columnIndex instead of 1 – span count

so then we can just return the amount of columns that this is supposed to fill, meaning one plus the amount of extra columns

So now if we run the code, what we can see, is that each category is withing its own boundaries, cool!

This may not be obvious from the preview above, but if you look closely the last item within each section(read: color) is spanning to fill the rest of the row.

Now we only need to add the headers for each section, let the fun begin!

So we need to implement RecyclerView.ItemDecoration, which has two important methods, onDrawOver to draw over the RecyclerView, which handles the drawing of the header view, and getItemOffsets, which provides the offsets to the views, giving enough space for the header in this case, but the possibilities are limitless!

So first lets give enough space for the headers by implementing getItemOffsets!

This method in my case only checks if the item is under header, and if so, gives it top padding from the header height

To determine if item is under header we use isUnderHeader method which is implemented as follows:

We just loop the range of span count and check if there’s any item that has different header id than the given position, and if so, we know that this item belongs under header and needs to be given space!

now we can implement the drawing of the header, this function is a bit more exotic to handle the stickiness of headers, meaning moving the topmost header upwards when it is pushed by the following header.

so first we perform couple of sanity checks to avoid useless drawing, we check if there actually is any items within the RecyclerView and if not, we just return to avoid useless work

then we initialize the value to store the preceeding headers top value to perform the pusheroo of topmost header, as commented

then for each item in backward order we do the following:

check if header should be drawn, by checking if is first item, which always should show header, making the sticky header effect, or if is first under any header

and the checking if the item is last header is quite simple, we just check if the preceding item has different header than current

and the item is first in the whole RecyclerView or first within its category, we now know that we can draw the header for this particular item, so we do that!

first we get the header item

which works in similar fashion as in timeshops implementation, we just call adapters get/bind header methods to obtain the header bound for this header id

then we just calculate the translations for this header, to draw it on top of the item in question

then we set these values for a rect to pass them around and to more easily apply moving when translation topmost header to give room for the next

Then we adjust the rect position if it is pushed by the following header, aka if the current headers bottom is smaller than highest top, meaning the previously processed headers top

then we actually draw the header

and what this method call does is just draws the view on the given canvas within the rect

and then just set the highestTop to current headers top

and I think we’re about to be done here!

After a quick run we can see it works as expected!

However this is just a simple and quick implementation and we might be missing something here, but for my simple use case it works, and if I find out anything, I’ll update here!

Should of probably used thicker header on the preview but… meh!

Thanks for reading anyway!

  • Mohamed Amine

    can you please explain more or share all the classes.
    Thank you !!