How to add a fast-scroller to the RecyclerView
Asked Answered
C

11

77

Background

On ListView, you could have a fast scroller which allowed you to drag a scrollbar to easily scroll to wherever you wished (using fastScrollEnabled attribute)

Together with "SectionIndexer" class and optionally some attributes, you could have a nice popup that shows as you use this scrollbar (link here).

Such a thing is shown on the contacts app so that you could scroll easily to specific letters.

The problem

RecyclerView doesn't seem to have any of those. Not even a fast-scroll.

The question

How do I add a fast scroller functionality for the RecyclerView?

Cystic answered 6/1, 2015 at 11:59 Comment(0)
C
31

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);
      }
    }
  }
Cystic answered 4/4, 2015 at 21:36 Comment(21)
What if your views vary in size? That is my problem right now. I do know what views would change. But not sure if I can use that to help me. I have several views that double in height.Bahamas
Yeah, but this is lots of help. Its just a matter of me figuring out how to compensating for those offsets.Bahamas
@LoyalRayne Please use the Github repo though. Here it might be un-updated.Cystic
Why my FastScroller doesn't include all the recycler view?? I follow all your step..but it doesn't show correctly, please help me @androiddeveloperJosefinejoseito
@MicheleLacorte Please try the github repo. It works well there.Cystic
You mean the code on github ? I tried @androiddeveloperJosefinejoseito
@MicheleLacorte Seems I forgot to add an import. Try now.Cystic
You're the best !, another simple question how come with the same settings your layout bar me ends up " under " the toolbar? I state that I use a collapsing toolbar..it's strange...Josefinejoseito
@MicheleLacorte I don't understand the question.Cystic
this bug : s11.postimg.org/52skjui03/Screenshot_2015_10_08_19_22_15.png @androiddeveloperJosefinejoseito
@MicheleLacorte I don't see this on my sample. How did you manage to do it on my sample ?Cystic
I use a scrollable toolbar, recyclerView has paddingTop = " ?Attr/actionBarSize" , for FastScroller use all his tricks layout, but when I get to the letter " A" the FastScroller continues to rise, doesn't stop at the beginning of the toolbar!..Josefinejoseito
@MicheleLacorte I don't understand. Is my sample ok or not for you? Please create a minimal sample, and ask a new question thread here. I can't help without understanding what you did.Cystic
No, not good , it's simple just look at the image above and you understand that the FastScroll should not stand in the toolbar is not it? @androiddeveloperJosefinejoseito
@MicheleLacorte For me my sample works fine . I think you've changed it.Cystic
Can I use this lib with minsdk 9+ ?? If not are there any alternatives that support minsdk 9? Gingerbread is really important to me (unfortunately)Hatshepsut
@ThanosF I think it can have nineoldandroids for the animations part and this will make it support API 9 and above. I wanted to add this but didn't have the chance yet.Cystic
This is a nice solution, but, it doesn't automatically hide when the user has stopped scrolling: How can we modify the code to hide the Scroll View when the user has stopped scrolling?Rumph
@Rumph You can use this: developer.android.com/reference/android/support/v7/widget/… , and when onScrollStateChanged is called, decide if you wish to show the fast-scroller or not: developer.android.com/reference/android/support/v7/widget/… . Do note that the fast-scroller is always visible on the contacts app.Cystic
this is a nice solution, but when I scrolls recycleview... handle view is not scrolling in starting and ending point..after some scroll its works...Orchestrate
@vishalpatel If you've found an issue on the repo, please publish a new issue on the repo website: github.com/AndroidDeveloperLB/…Cystic
B
53

I stumbled on this question a few days ago when I ran into this situation. Here is my example implementation of FastScroll for RecyclerView:

github.com/danoz73/RecyclerViewFastScroller

Try running the example application, and peruse the code to see a fairly simple usage of a simple RecyclerViewFastScroller widget. There is information on github, but I'll include the basic usage here for a vertical fast scroller.

For a full example, refer to the sample application in the repo.

Basic Usage

In the activity or fragment XML where your RecyclerView resides, include a VerticalRecyclerViewFastScroller object. The following example would be in a relative layout:

...
  <android.support.v7.widget.RecyclerView
      android:id="@+id/recyclerView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>


  <xyz.danoz.recyclerviewfastscroller.vertical.VerticalRecyclerViewFastScroller
      android:id="@+id/fast_scroller"
      android:layout_width="@dimen/however_wide_you_want_this"
      android:layout_height="match_parent"
      android:layout_alignParentRight="true"
    />
...

In your fragment or activity where you setup layout programmatically, hook up the fast scroller to the recycler:

...
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      View rootView = inflater.inflate(R.layout.recycler_view_frag, container, false);
      ...

      // Grab your RecyclerView and the RecyclerViewFastScroller from the layout
      RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerView);
      VerticalRecyclerViewFastScroller fastScroller = (VerticalRecyclerViewFastScroller) rootView.findViewById(R.id.fast_scroller);

      // Connect the recycler to the scroller (to let the scroller scroll the list)
      fastScroller.setRecyclerView(recyclerView);

      // Connect the scroller to the recycler (to let the recycler scroll the scroller's handle)
      recyclerView.setOnScrollListener(fastScroller.getOnScrollListener());

      ...
      return rootView;
  }
...

Hope this helps!

EDIT: There is now added support for Android-Lollipop-Contacts-style section indicators! Check out the sample application's implementation for details.

Butcherbird answered 13/2, 2015 at 7:7 Comment(10)
Nice. Didn't like the colors of the items though :) . Can it handle the SectionIndexer somehow ? For example, to show a bubble like on Lollipop's contacts app, of the current letter ?Cystic
I'll be adding additional implementations like that this week! Just wanted to show a basic example first :)Butcherbird
ok, I will grant you the reward, but I still want to see those features ... :) Would also be nice to see horizontal scrolling, and making it look Material design like on Lollipop.Cystic
I've updated the project to include a SectionIndicator that can interface with a SectionIndexer. Check out the github for details.Butcherbird
Thank you. Hope it will become easier to switch between ListView/GridView and RecyclerView now.Cystic
@DanielSmith why the min SDK start from 14?? Please return it to at least API9+Jenks
If you using VerticalRecyclerViewFastScroller with SwipeRefreshLayout don't put fastScroller into it, put it outside.Prepositive
Observed this is not smooth on scroll. It is jumping.Countrywoman
Doesn't support StaggeredGridLayoutManagerPheni
2 years has been passed, and it's not under active development. So many unresolved issuesRao
C
31

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);
      }
    }
  }
Cystic answered 4/4, 2015 at 21:36 Comment(21)
What if your views vary in size? That is my problem right now. I do know what views would change. But not sure if I can use that to help me. I have several views that double in height.Bahamas
Yeah, but this is lots of help. Its just a matter of me figuring out how to compensating for those offsets.Bahamas
@LoyalRayne Please use the Github repo though. Here it might be un-updated.Cystic
Why my FastScroller doesn't include all the recycler view?? I follow all your step..but it doesn't show correctly, please help me @androiddeveloperJosefinejoseito
@MicheleLacorte Please try the github repo. It works well there.Cystic
You mean the code on github ? I tried @androiddeveloperJosefinejoseito
@MicheleLacorte Seems I forgot to add an import. Try now.Cystic
You're the best !, another simple question how come with the same settings your layout bar me ends up " under " the toolbar? I state that I use a collapsing toolbar..it's strange...Josefinejoseito
@MicheleLacorte I don't understand the question.Cystic
this bug : s11.postimg.org/52skjui03/Screenshot_2015_10_08_19_22_15.png @androiddeveloperJosefinejoseito
@MicheleLacorte I don't see this on my sample. How did you manage to do it on my sample ?Cystic
I use a scrollable toolbar, recyclerView has paddingTop = " ?Attr/actionBarSize" , for FastScroller use all his tricks layout, but when I get to the letter " A" the FastScroller continues to rise, doesn't stop at the beginning of the toolbar!..Josefinejoseito
@MicheleLacorte I don't understand. Is my sample ok or not for you? Please create a minimal sample, and ask a new question thread here. I can't help without understanding what you did.Cystic
No, not good , it's simple just look at the image above and you understand that the FastScroll should not stand in the toolbar is not it? @androiddeveloperJosefinejoseito
@MicheleLacorte For me my sample works fine . I think you've changed it.Cystic
Can I use this lib with minsdk 9+ ?? If not are there any alternatives that support minsdk 9? Gingerbread is really important to me (unfortunately)Hatshepsut
@ThanosF I think it can have nineoldandroids for the animations part and this will make it support API 9 and above. I wanted to add this but didn't have the chance yet.Cystic
This is a nice solution, but, it doesn't automatically hide when the user has stopped scrolling: How can we modify the code to hide the Scroll View when the user has stopped scrolling?Rumph
@Rumph You can use this: developer.android.com/reference/android/support/v7/widget/… , and when onScrollStateChanged is called, decide if you wish to show the fast-scroller or not: developer.android.com/reference/android/support/v7/widget/… . Do note that the fast-scroller is always visible on the contacts app.Cystic
this is a nice solution, but when I scrolls recycleview... handle view is not scrolling in starting and ending point..after some scroll its works...Orchestrate
@vishalpatel If you've found an issue on the repo, please publish a new issue on the repo website: github.com/AndroidDeveloperLB/…Cystic
L
13

The Android Support Library 26.0.0 now supports fastScrollEnabled

New fastScrollEnabled boolean flag for RecyclerView.

If enabled, fastScrollHorizontalThumbDrawable, fastScrollHorizontalTrackDrawable, fastScrollVerticalThumbDrawable, and fastScrollVerticalTrackDrawable must be set.

Sample - https://android.jlelse.eu/fast-scrolling-with-recyclerview-2b89d4574688

Laminar answered 25/7, 2017 at 11:47 Comment(5)
Interesting! So how do you show the bubble with the text, as you drag it?Cystic
Just to mention that those drawables must be StateListDrawable's and you also have to set drawables for both axis.Phineas
i've just implement fastScroll use support library 26.0.1, it's only show fast scroll bar, must custom to show bubble textMinnich
What do you mean by drawable must be StateListDrawable?Preternatural
@Preternatural statelist drawable is when you implement an item with state_pressed="true", item with state_selected="true" and just the normal item within the selector. So you will have 3 items in the selector.Shaff
L
11

There are a lot of unanswered questions about RecyclerView and fast-scroll/section indexer, let's try to regroup and gather our opinions and information here.

The short answer is: NO, you can't enable the fast-scroll because RecyclerView does not contain a FastScroller object and nor any related logical-state variables. This because RecyclerView is not an AbsListView.

On the other hand, it's not impossible to implement a RecyclerView which contains a dumped version of FastScroller and the necessary logic for the fast-scrolling, but I have not seen any implementation of this so far.

Please share your consideration about that or if you think I'm wrong.

Lengthways answered 18/1, 2015 at 19:1 Comment(3)
This is a nice idea. Hope I will have the time to try it out and implement it myself.Cystic
RecyclerView was not meant to be used with a FastScroller. It is agains its philosophy and design idea of infinite list. But there is nothing that will stop one from trying though :-)Twentytwo
Please delete your answer as it will confuse everyone now that it's completely wrongGuttate
S
5

FastScroller functionality is added from android library 26.0.0 for RecyclerView

compile dependency

    compile 'com.android.support:recyclerview-v7:26.1.0'
    compile 'com.android.support:design:26.1.0'

add depedency to project.gradle

     maven {
            url "https://maven.google.com"
        }

your recyclerview.xml file

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    xmlns:tool="http://schemas.android.com/tools"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    tool:context=".MainActivity">
    <android.support.v7.widget.RecyclerView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:id="@+id/songlist"
                android:layout_marginStart="8dp"
                android:layout_marginEnd="8dp"
                app:fastScrollEnabled="true"
              app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
                app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
                app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
                app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
               /></LinearLayout>

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"
        android:bottomRightRadius="44dp" />

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

    <solid android:color="#f73831" />

</shape>

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="@color/dark_grey" />

    <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:drawable="@drawable/thumb"
        android:state_focused="true"
        android:state_pressed="true" />

    <item android:drawable="@drawable/thumb"
        android:state_focused="false"
        android:state_pressed="true" />
    <item android:drawable="@drawable/thumb" 
            android:state_focused="true" />
    <item android:drawable="@drawable/thumb"
        android:state_focused="false"
        android:state_pressed="false" />
</selector>

line_drawble.xml

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

    <item android:drawable="@drawable/line"
        android:state_focused="false"
        android:state_pressed="true" />
    <item android:drawable="@drawable/line" 
            android:state_focused="true" />
    <item android:drawable="@drawable/line"
        android:state_focused="false"
        android:state_pressed="false" />
</selector>
Soudan answered 21/2, 2018 at 12:16 Comment(7)
Some resources are missing: line_dark , line_dark , dark_grey . Where did you find this information? Is there a tutorial/article about it? When was it added (time) ?Cystic
@androiddeveloper , i forget to change some elements name (i put as same as my code), i didnt find this information, above code is part of my self project ,i currently working on. i think is good share info, that i how i slove this problem.Soudan
How did you find about it, then? And can you please update it so that I can try it out?Cystic
@androiddeveloper upvote here is link of documentation developer.android.com/topic/libraries/support-library/…Soudan
and for more modification use this documentation : developer.android.com/reference/android/support/v7/widget/…Soudan
I don't see in the docs of RecyclerView (here: developer.android.com/reference/android/support/v7/widget/… ) anything about fast-scroller. In fact when I search for "fast" nothing is there... Where did you find what needs to be done, and how the drawables should be used and be set?Cystic
I've now tested it. Seems to work well. I've upvoted this solution. Here's a nice article about it: android.jlelse.eu/fast-scrolling-with-recyclerview-2b89d4574688Cystic
B
4

You can as well use A-Z Fastscroll for RecyclerView. It is the iOS style.

https://github.com/code-computerlove/FastScrollRecyclerView/

How to use it:

  • Replace android.support.v7.widget.RecyclerView with com.codecomputerlove.fastscrollrecyclerviewdemo.FastScrollRecyclerView
  • Your adapter needs to implement FastScrollRecyclerViewInterface and override getMapIndex(). The function should return the mapIndex. Look into calculateIndexesForName() for inspiration on how to create it. Once created pass it to the adapter in the constructor.
  • Create an instance of FastScrollRecyclerViewItemDecoration and add it on your RecyclerView FastScrollRecyclerViewItemDecoration decoration = new FastScrollRecyclerViewItemDecoration(this); mRecyclerView.addItemDecoration(decoration);
  • add <dimen name="fast_scroll_overlay_text_size">100dp</dimen> to your /values/dimens.xml file. This is the dp size of the overlayed letter
Bielefeld answered 6/3, 2015 at 12:45 Comment(1)
This is actually nice, but it's IOS style. The fast-scroller is for Android.Cystic
O
3

You can try our lib: https://github.com/FutureMind/recycler-fast-scroll. It's still in early development, but was built specifically to deal with smoothness issue we experienced with other libraries. It uses a little bit different mechanism. It supports horizontal LayoutManager as well and will also support multi-column setups in near future.

Edit: there are some neat customisation options now.

Outvote answered 4/12, 2015 at 19:21 Comment(4)
RecyclerView works on API 7 and above. Why do you need API 15 for just a simple view? Also, it has a bug: use the fast scroller to scroll somewhere, then scroll normally. The scoller will jump from its current position.Cystic
The reason is probably the same as in your repo. I could change it to 11 immediately but I will change it to 7 when I have time to port animations to 7. Thanks for noticing this bug, I must have introduced it after adding horizontal scroll featureBibbs
Sorry. I didn't remember what I did back then. But I did plan on making it work on pre-API 11.Cystic
This one supports StaggeredGridLayoutManager out of the boxPheni
O
1

There is a provision of implementing scroll bars with RecycleView and its LayoutManager.

For example: computeVerticalScrollExtent() , computeVerticalScrollOffset() and computeVerticalScrollRange() can provide information for always positioning a vertical scroll thumb at correct place.

These methods are also there in LayoutManager for delegating actual measurements. So the LayoutManager implementation used must support these measurements.

Also, drag touch on scroll thumb can be intercepted by overriding onInterceptTouchEvent() of RecyclerView. And after calculating the desired scroll, scrollTo() can be called to update RecyclerView.

Ovaritis answered 12/2, 2015 at 19:4 Comment(1)
Can you please demonstrate it by code? I asked this in order to really find a way to put the fast-scroller, and as I'm a newb in RecyclerView, it's quite hard for me to do it on my short spare timeCystic
D
1

This new library was based on framework launcher's fast scroller: https://github.com/zhanghai/AndroidFastScroll

Usage:

new FastScrollerBuilder(recyclerView).build();

Optionally implement PopupTextProvider in the adapter.

Downey answered 10/12, 2019 at 11:19 Comment(1)
Cool! Thanks. Could be usefulCystic
P
0

Just enable fast scrolling and add thumb, tracker for scroll bar like below.

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_sensors"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable" />
Pomade answered 7/8, 2018 at 7:19 Comment(1)
Yes, this question was asked way before this thing was possible. I wonder though how customizable it is. Is it possible to set how the bubble looks like? Is it possible to even have the bubble? How can I set what to show in the bubble?Cystic
C
0

The fast-scroller in android studio is still very buggy as it's only based on xml,so I went digging for external libraries that could implement fast scrolling. You can get it here. Very easy to implement and customize.

Cashandcarry answered 20/7, 2020 at 6:58 Comment(1)
Seems nice, but also seems too strict about the rules. Requiring us to know the height of each item, and also having the same height for each item type...Cystic

© 2022 - 2024 — McMap. All rights reserved.