RecyclerView custom Thumb Size & Length

First things first, imma say: This might not work well with RecyclerViews which have a lot of elements, I couldn’t test it properly yet(I didn’t test many things). And overall, it might not be the prettiest example.
I’m a bit new in Android Development, and posting articles as well, so this will probably be a little messy at start. Also, I know this still has some issues, I’m hoping to motivate myself a bit more with writing this article.

So, to start off, something I worked on required the thumb size from scrollbar to be wider than scrollbar track:

It was a bit weird that android didn’t have a built in field/method/anything to change this, but after a bit of work, I managed to overcome this hurdle by adding transparent ‘pixels’, as a Stroke field.

scrollbar_track.xml

Why layer-list? I couldn’t manage to properly add padding/margins, and this inset was what helped me accomplish this, so it looks nice. You can manipulate the ‘offsets’ with top/right/bottom/left properties in <item>.

You can control the track & thumb width by using the <size android:width> , (android:height -horizontal) for property, in combination with the transparent stroke. As it happens, android:scrollbarSize xml property is also useless(at least visually, didn’t need its functionality).

& The code for thumb_shape.xml

Another answer to a potential question, what about <size> property? Well, as it happens - it’s useless here.

styles.xml

You can play with the fades & duration, to tailor it for your needs, but i left it visible here just for debugging. RecyclerView code in xml, just need to add the style.

Up to this point, we created it just visually. It looks nice, but another thing I happened to need was thumb length, which wasn’t modifiable by default. So to cut the story short, after hours & hours of trial and error, I ended up creating a custom RecyclerView which implements the ScrollingView.
It’s purpose: Create your own thumb length.

But it didn’t end with that. To make it all work properly, you have to define the track length, and calculate the offset. And yeah, more hours and hours of work later, I came up with the following…

(I wanted to separate this into 2 parts, but i think it’s better to keep it all in a single one, even if a bit large, no? Up to this point as I’ve said, we created this just visually, we will do some modifications on top of that starting from now.)

To start off, inside the values folder, if you don’t already have, create a new file: attrs.xml

CustomThumbSizeRecyclerView is the name I so conveniently chose for this, quite catchy, right?
It only has a single custom field so far, what I’m hoping to accomplish by writing this is to properly add the field for width(thickness) & color into this, along with several other options, which isn’t going well at the moment, but all in due time.

Rant(ish) mode: off, now to the actual Custom View code, Kotlin version:

Alright, so I just dumped the code on you up there, so I’ll go step by step over it now:

First of all, we created a Custom View element, if you want details about how this works, look for a tutorial about that(In other words, I don’t know how to explain it properly :)). The point is, we made the Custom View extend RecyclerView, and implement ScrollingView interface. Now, the reason that i bolded the ScrollingView now, is just because this part was a pain in the #$$ to work with.

To properly define Thumb length, and the offset for scrolling, we need to create 3 variables. We initialize thumbLength in the init block, based off of the property we added in attrs.xml, which we will later use to add the Thumb Length attribute field while creating our view, whether that’s in layout or in code. That’s also the reason thumbLength is not private, so we can use it in code.

Now, I’ll go over vertical calculations, horizontal ones should work just the same(I still didn’t test that part. :) )

computeVerticalScrollRange() calculates the vertical range for the track, in other words: track length, the long thin line on the right side usually, its total length(range). As a side-note, if trackLength isn’t initialized at this point, we initialize it. Though this part might be placed at some other part, didn’t play with that part much(yet).

Now onto the next part, computeVerticalScrollExtent(). For those of you which aren’t reading the documentation(Ctrl+Q on windows/linux), this actually refers to thumbLength. Basically, the ‘reason’ I even tried creating this. And aside from adding this, we needed to adapt the computeVerticalScrollOffset so it works with the value of thumbLength that we wanted.

To compute the scroll offset, first thing we needed was to get the height of the visible RecyclerView, and the invisible part. super.computeVerticalScrollOffset returns the up-most pixel on the screen(VERTICAL RecyclerView), in other words, the ‘highest’ pixel. When we subtract the value of this up-most pixel from the invisible part of the RecyclerView, we will get the remaining amount of scrollable pixels (scrollAmountRemaining).
Based on that, we have 3 scenarios:
#1 When the invisible amount of pixels is equal to scrollAmountRemaining->it means that we are at the top of the RecyclerView.
#2 When the scrollAmountRemaining is above 0->We still have ‘scrollable’ space remaining(RecyclerView didn’t reach the end).
#3 When the scrollAmountRemaining is 0(or less than 0)-> We reached the end of the RecyclerView, no space left for scrolling.

To get the heights, first thing we had to do was to manually trigger the (View.)measure function. The way that works is basically(As I’ve understood) that it removes any length constraint this view had. So theoretically this could be infinite, and this is the reason I’m not sure how this works with RecyclerViews that have a lot of elements.
Continuing, we ‘write down’ the height value before we call the measure function, and when the function is done, we write down the unconstrained height into totalLength.
After that, by calling measure, and providing our value before measuring, and MeasureSpec.AT_MOST we restore the RecyclerView back to its original state. And voilà, we got the track length without messing up the views.

I don’t think I need to mention what dpToPx() does, if you don’t know anything about that, then just google some more.

Next on our agenda, is to add app:thumbLength=”Xdp” into the RecyclerView tag, also we modify it from being a RecyclerView element, into our CustomThumbSizeRecyclerView:

An example of how we can instantiate the CustomRecyclerView inside our Activity/Fragment:

That’s all, folks!

GitHub link, you can find the java code for this in there, and this should be used just as a reference.(I was following a different tutorial about RV with multiple view types, and something about notifications, but you don’t need to look at the adapter/MainActivity at all.)

Also I hope this helped someone, the actual code is rather simple(and a bit long with my explanations, I tried to be as non-technical in them as possible, was it too much though?), but I had nothing else to use as a reference, so I did this with lots of trial & error. Honestly I’d love to work some more on this, and post this when it’s truly polished, but I doubt this will be happening in the near future as it stands .-.

I’m looking forward to your critics, I know of several more bugs(such as thumbLength on rotation- if it’s too long, dragging the scrollbar thumb, etc), and I didn’t even get to try to fix them at this point, and probably won’t in the following week/2(life I guess) for sure, but hopefully I’ll get the motivation to properly go over them.

As time passes, I’ll maybe update this article, and/or/also maybe create a library? But so far I didn’t need this for any other purpose than looking nice, and I achieved that.

And if you’re actually reading this part, thanks for hanging in there so far, you deserve kudos for that at the very least, and honestly, this scrollbar thumb on my browser is scaring me at this point..