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:
make a RecyclerView.Adapter that implements BubbleTextGetter, which given a position in the data will return the text to show in the bubble-popup.
position the FastScroller inside the layout that container the RecyclerView (probably at the right area).
Customize the FastScroller FastScroller
Some disadvantages:
- doesn't support orientation change, but it's probably easy to fix.
- doesn't support other layoutManagers. Only LinearLayoutManager
- 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 :
- fastScrollEnabled : boolean value to enable the fast scrolling. Setting this as true will require that we provide the following four
properties. - fastScrollHorizontalThumbDrawable : A StateListDrawable that will be used to draw the thumb which will be draggable across the
horizontal axis. - fastScrollHorizontalTrackDrawable : A StateListDrawable that will be used to draw the line that will represent the scrollbar on
horizontal axis. - fastScrollVerticalThumbDrawable : A StateListDrawable that will be used to draw the thumb which will be draggable on vertical axis.
- 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
How to Implement Drag and Drop in Android 2.2
Android Viewmodel Additional Arguments
Android - Extracting Cookies After Login in Webview
Java.Lang.Nullpointerexception (No Error Message)
How to Parse This Nested JSON Array in Android
Sms Authentication on Android Nullpointerexception
How to Exclude Certain Messages by Tag Name Using Android Adb Logcat
How to View the Shared Preferences File Using Android Studio
Refresh Fragment UI from Fragmentactivity
How to Display Input Errors in Popup
How to Programmatically Set the Background Color Gradient on a Custom Title Bar
How to Create a Relativelayout Programmatically with Two Buttons One on Top of the Other
Android Broadcastreceiver, Auto Run Service After Reboot of Device
Gradle Error: Minimum Supported Gradle Version Is 3.3. Current Version Is 3.2
Gradle Dsl Method Not Found: 'Compile()'
Appropriate Multiplication of Matrices for Rotation/Translation