How to Add a Fast-Scroller to the Recyclerview

How to add a fast-scroller to the RecyclerView

New answer:
Over time I've noticed that my original answer had some drawbacks compared to other solutions, especially for fragments within ViewPager.

I suggest to either use the android-x solution in case you don't need a bubble, or a third party library (here is a nice one) in case you do.


old answer:

Since all third party libraries had issues, I've decided to gather what I can find (mostly from here), fix everything and publish my own POC of the fast-scroller of the RecyclerView :

https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller

usage:

  1. make a RecyclerView.Adapter that implements BubbleTextGetter, which given a position in the data will return the text to show in the bubble-popup.

  2. position the FastScroller inside the layout that container the RecyclerView (probably at the right area).

  3. Customize the FastScroller FastScroller

Some disadvantages:

  1. doesn't support orientation change, but it's probably easy to fix.
  2. doesn't support other layoutManagers. Only LinearLayoutManager
  3. Needs API 11 and above.

Code:

BubbleTextGetter

public interface BubbleTextGetter
{
String getTextToShowInBubble(int pos);
}

recycler_view_fast_scroller__fast_scroller.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent">

<TextView
android:id="@+id/fastscroller_bubble"
android:layout_gravity="right|end"
android:gravity="center"
android:textSize="48sp" tools:text="A"
android:layout_width="wrap_content"
android:textColor="#FFffffff"
android:layout_height="wrap_content"
android:background="@drawable/recycler_view_fast_scroller__bubble"
android:visibility="visible"/>

<ImageView
android:id="@+id/fastscroller_handle"
android:layout_width="wrap_content"
android:layout_marginRight="8dp"
android:layout_marginLeft="8dp"
android:layout_height="wrap_content"
android:src="@drawable/recycler_view_fast_scroller__handle"/>

</merge>

MainActivity

...
fastScroller=(FastScroller)findViewById(R.id.fastscroller);
fastScroller.setRecyclerView(recyclerView);

FastScroller

public class FastScroller extends LinearLayout
{
private static final int BUBBLE_ANIMATION_DURATION=100;
private static final int TRACK_SNAP_RANGE=5;

private TextView bubble;
private View handle;
private RecyclerView recyclerView;
private final ScrollListener scrollListener=new ScrollListener();
private int height;

private ObjectAnimator currentAnimator=null;

public FastScroller(final Context context,final AttributeSet attrs,final int defStyleAttr)
{
super(context,attrs,defStyleAttr);
initialise(context);
}

public FastScroller(final Context context)
{
super(context);
initialise(context);
}

public FastScroller(final Context context,final AttributeSet attrs)
{
super(context,attrs);
initialise(context);
}

private void initialise(Context context)
{
setOrientation(HORIZONTAL);
setClipChildren(false);
LayoutInflater inflater=LayoutInflater.from(context);
inflater.inflate(R.layout.recycler_view_fast_scroller__fast_scroller,this,true);
bubble=(TextView)findViewById(R.id.fastscroller_bubble);
handle=findViewById(R.id.fastscroller_handle);
bubble.setVisibility(INVISIBLE);
}

@Override
protected void onSizeChanged(int w,int h,int oldw,int oldh)
{
super.onSizeChanged(w,h,oldw,oldh);
height=h;
}

@Override
public boolean onTouchEvent(@NonNull MotionEvent event)
{
final int action=event.getAction();
switch(action)
{
case MotionEvent.ACTION_DOWN:
if(event.getX()<handle.getX())
return false;
if(currentAnimator!=null)
currentAnimator.cancel();
if(bubble.getVisibility()==INVISIBLE)
showBubble();
handle.setSelected(true);
case MotionEvent.ACTION_MOVE:
setPosition(event.getY());
setRecyclerViewPosition(event.getY());
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handle.setSelected(false);
hideBubble();
return true;
}
return super.onTouchEvent(event);
}

public void setRecyclerView(RecyclerView recyclerView)
{
this.recyclerView=recyclerView;
recyclerView.setOnScrollListener(scrollListener);
}

private void setRecyclerViewPosition(float y)
{
if(recyclerView!=null)
{
int itemCount=recyclerView.getAdapter().getItemCount();
float proportion;
if(handle.getY()==0)
proportion=0f;
else if(handle.getY()+handle.getHeight()>=height-TRACK_SNAP_RANGE)
proportion=1f;
else
proportion=y/(float)height;
int targetPos=getValueInRange(0,itemCount-1,(int)(proportion*(float)itemCount));
recyclerView.scrollToPosition(targetPos);
String bubbleText=((BubbleTextGetter)recyclerView.getAdapter()).getTextToShowInBubble(targetPos);
bubble.setText(bubbleText);
}
}

private int getValueInRange(int min,int max,int value)
{
int minimum=Math.max(min,value);
return Math.min(minimum,max);
}

private void setPosition(float y)
{
int bubbleHeight=bubble.getHeight();
int handleHeight=handle.getHeight();
handle.setY(getValueInRange(0,height-handleHeight,(int)(y-handleHeight/2)));
bubble.setY(getValueInRange(0,height-bubbleHeight-handleHeight/2,(int)(y-bubbleHeight)));
}

private void showBubble()
{
AnimatorSet animatorSet=new AnimatorSet();
bubble.setVisibility(VISIBLE);
if(currentAnimator!=null)
currentAnimator.cancel();
currentAnimator=ObjectAnimator.ofFloat(bubble,"alpha",0f,1f).setDuration(BUBBLE_ANIMATION_DURATION);
currentAnimator.start();
}

private void hideBubble()
{
if(currentAnimator!=null)
currentAnimator.cancel();
currentAnimator=ObjectAnimator.ofFloat(bubble,"alpha",1f,0f).setDuration(BUBBLE_ANIMATION_DURATION);
currentAnimator.addListener(new AnimatorListenerAdapter()
{
@Override
public void onAnimationEnd(Animator animation)
{
super.onAnimationEnd(animation);
bubble.setVisibility(INVISIBLE);
currentAnimator=null;
}

@Override
public void onAnimationCancel(Animator animation)
{
super.onAnimationCancel(animation);
bubble.setVisibility(INVISIBLE);
currentAnimator=null;
}
});
currentAnimator.start();
}

private class ScrollListener extends OnScrollListener
{
@Override
public void onScrolled(RecyclerView rv,int dx,int dy)
{
View firstVisibleView=recyclerView.getChildAt(0);
int firstVisiblePosition=recyclerView.getChildPosition(firstVisibleView);
int visibleRange=recyclerView.getChildCount();
int lastVisiblePosition=firstVisiblePosition+visibleRange;
int itemCount=recyclerView.getAdapter().getItemCount();
int position;
if(firstVisiblePosition==0)
position=0;
else if(lastVisiblePosition==itemCount-1)
position=itemCount-1;
else
position=firstVisiblePosition;
float proportion=(float)position/(float)itemCount;
setPosition(height*proportion);
}
}
}

Fast Scrolling on RecyclerView

Remove the ScrollView from your layout, and don't use wrap_content for the RecyclerView's height. RecyclerView already provides scrolling by itself, and you can use whatever layout_ attributes you had on the ScrollView to make the RecyclerView the right size.

How to use fastScrollEnabled in RecyclerView?

With Support Library 26, we can easily enable fast scrolling for RecyclerView. Let’s get to it!

Let’s go over each property one by one :

  1. fastScrollEnabled : boolean value to enable the fast scrolling. Setting this as true will require that we provide the following four
    properties.
  2. fastScrollHorizontalThumbDrawable : A StateListDrawable that will be used to draw the thumb which will be draggable across the
    horizontal axis.
  3. fastScrollHorizontalTrackDrawable : A StateListDrawable that will be used to draw the line that will represent the scrollbar on
    horizontal axis.
  4. fastScrollVerticalThumbDrawable : A StateListDrawable that will be used to draw the thumb which will be draggable on vertical axis.
  5. fastScrollVerticalTrackDrawable : A StateListDrawable that will be used to draw the line that will represent the scrollbar on
    vertical axis.

add in build.gradle

    dependencies {
....
compile 'com.android.support:design:26.0.1'
compile 'com.android.support:recyclerview-v7:26.0.1'
....
}

Since Support Library 26 has now been moved to Google’s maven repository, let’s include that in our project level build.gradle

allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
}

activity_main.xml

<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable">

</android.support.v7.widget.RecyclerView>

add below four xml file in your drawable folder,

line_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/line"/>

<item
android:drawable="@drawable/line"/>
</selector>

line.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<solid android:color="@android:color/darker_gray" />

<padding
android:top="10dp"
android:left="10dp"
android:right="10dp"
android:bottom="10dp"/>
</shape>

thumb_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/thumb"/>

<item
android:drawable="@drawable/thumb"/>
</selector>

thumb.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<corners
android:topLeftRadius="44dp"
android:topRightRadius="44dp"
android:bottomLeftRadius="44dp" />

<padding
android:paddingLeft="22dp"
android:paddingRight="22dp" />

<solid android:color="@color/colorPrimaryDark" />

</shape>

Android L: Fast scroll for the RecyclerView

The only thing you will find in RecyclerView is the basic implementation of the recycling logic. It is the complete polar opposite of ListView in that it offers you maximum customisability (you can achieve any unique layout you want unlike ListView), but it has almost nothing built in with it (unlike ListView which has numerous features like the fast scroll thumb).

If you want to add something like the fast scroll feature, you're going to need to develop it on your own for now.

New fastScrollEnabled boolean flag for RecyclerView. If enabled, fastScrollHorizontalThumbDrawable, fastScrollHorizontalTrackDrawable, fastScrollVerticalThumbDrawable, and fastScrollVerticalTrackDrawable must be set. Now available on Support Library 26.0.0



Related Topics



Leave a reply



Submit