RecyclerView fast scroll thumb height too small for large data set
I am using the default RecyclerView
fast scroll and I followed this guide to support it.
Now, the problem is that the thumb resizes its height as per the size of the data set. For large items like 100 and above, the thumb becomes very small and almost becomes difficult to respond to dragging.
Please is there any way I can set minimum height for the fast scroll thumb.
This is only a partial answer; I'm missing (at least) one piece of the puzzle, but hopefully someone else can figure it out.
Once you've added the necessary attributes to your <RecyclerView>
tag (as mentioned in the answer linked in OP's question), the sizing/positioning of the scrollbar thumb is controlled by three methods inside LinearLayoutManager
:
int computeVerticalScrollRange()
: The size of the scrollbar's track.
int computeVerticalScrollExtent()
: The size of the scrollbar's thumb.
int computeVerticalScrollOffset()
: The distance between the top of the scrollbar's track and the top of the scrollbar's thumb.
The units for these methods is arbitrary; you can use anything you'd like as long as all three methods share the same units. By default, LinearLayoutManager
will use one of two sets of units:
mSmoothScrollbarEnabled == true
: Use units based on the pixel sizes of the visible items in the RecyclerView
.
mSmoothScrollbarEnabled == false
: Use units based on the positions of the visible items in the RecyclerView
's adapter.
To control the size of the scrollbar's thumb yourself, you'll have to override these methods... but here's the piece I'm missing: In all of my experimentation, computeVerticalScrollExtent()
is never called by the system . That said, we can still show some progress here.
First, I've created a simple adapter that shows 500 CardView
s with the item's position inside. I've enabled fast scrolling with some really simple (but ugly) drawables. Here's what the scrollbar looks like with just a default LinearLayoutManager
implementation:
As you've found, with 500 (small) items, the scrollbar thumb is really small and quite hard to tap on. We can make the scrollbar dramatically larger by overriding computeVerticalScrollRange()
to just return a fixed constant... I picked 5000
essentially at random just to show the major change:
Of course, now the scrollbar doesn't work like you'd expect; scrolling the list by dragging on it as normal moves the thumb much more than it should, and fast scrolling the list by dragging on the thumb moves the list much less than it should.
On my device, with the randomly-chosen range of 5000
, overriding computeVerticalScrollOffset()
as follows makes the scrollbar thumb move perfectly as I scroll the list by dragging on it:
@Override
public int computeVerticalScrollRange(RecyclerView.State state) {
return 5000;
}
@Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
return (int) (super.computeVerticalScrollOffset(state) / 23.5f);
}
However, this still doesn't fix the second issue: dragging on the thumb itself doesn't correctly scroll the list. As I mentioned above, it would seem like the appropriate thing to do here would be to override computeVerticalScrollExtent()
, but the system never invokes this method. I've even overridden it to simply throw an exception, and my app never crashes.
Hopefully this at least helps point people in the right direction for a full solution.
PS: The implementations of computeVerticalScrollRange()
and computeVerticalScrollOffset()
I've included in this answer are intentionally simple (read: bogus). "Real" implementations would be much more complex; the default LinearLayoutManager
implementations take into account device orientation, the first and last visible items in the list, the number of items off-screen in both directions, smooth scrolling, various layout flags, and so on.
I solved this problem by copying the Fast Scroller classs from android.support.v7.widget.FastScroller
Then I removed the fast scroll enabled from the xml and applied fastscroller using the below codes
StateListDrawable verticalThumbDrawable = (StateListDrawable) getResources().getDrawable(R.drawable.fastscroll_sunnah);
Drawable verticalTrackDrawable = getResources().getDrawable(R.drawable.fastscroll_line_drawable);
StateListDrawable horizontalThumbDrawable = (StateListDrawable)getResources().getDrawable(R.drawable.fastscroll_sunnah);
Drawable horizontalTrackDrawable = getResources().getDrawable(R.drawable.fastscroll_line_drawable);
Resources resources = getContext().getResources();
new FastScroller(recyclerView, verticalThumbDrawable, verticalTrackDrawable,
horizontalThumbDrawable, horizontalTrackDrawable,
resources.getDimensionPixelSize(R.dimen.fastscroll_default_thickness),
resources.getDimensionPixelSize(R.dimen.fastscroll_minimum_range),
resources.getDimensionPixelOffset(R.dimen.fastscroll_margin));
Inside the FastScroller Class I extended the defaultWidth
FastScroller(RecyclerView recyclerView, StateListDrawable verticalThumbDrawable,
Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable,
Drawable horizontalTrackDrawable, int defaultWidth, int scrollbarMinimumRange,
int margin) {
...
this.defaultWidth = defaultWidth;
...
Then I updated the code in this method
void updateScrollPosition(int offsetX, int offsetY) {
...
mVerticalThumbHeight = Math.max(defaultWidth * 4, Math.min(verticalVisibleLength,
(verticalVisibleLength * verticalVisibleLength) / verticalContentLength));
...
...
mHorizontalThumbWidth = Math.max(defaultWidth * 4, Math.min(horizontalVisibleLength,
(horizontalVisibleLength * horizontalVisibleLength) / horizontalContentLength));
...
}
This ensures that the minimum thumb height/width is 4 times the default width
This is a known issue, opened in August 2017: https://issuetracker.google.com/issues/64729576
Still waiting for recommendations on how to manage RecyclerView
with fast scroll on large amounts of data. No reply from Google on that issue so far on recommendations :(
The answer from Nabil is similar to a workaround mentioned in the issue. Nabil's answer copies the FastScroller
class and modifies it to ensure a minimum thumb size, and the workaround in the issue extends FastScroller
(but it has to stay in the android.support.v7.widget
package) to ensure the minimum thumb size.