如何添加快速滚动到RecyclerView

时间:2015-01-06 11:59:17

标签: android scrollbar android-recyclerview fastscroll sectionindexer

背景

在ListView上,您可以使用快速滚动条,您可以拖动滚动条轻松滚动到您希望的任何位置(使用fastScrollEnabled属性)

与" SectionIndexer"类和可选的一些属性,你可以有一个很好的弹出窗口,当你使用这个滚动条时显示(链接here)。

联系人应用程序上会显示这样的内容,以便您可以轻松滚动到特定字母。

问题

RecyclerView似乎没有任何这些。甚至不是快速滚动。

问题

如何为RecyclerView添加快速滚动功能?

11 个答案:

答案 0 :(得分:52)

几天前,当我遇到这种情况时,我偶然发现了这个问题。这是我的 FastScroll for RecyclerView的示例实现

github.com/danoz73/RecyclerViewFastScroller

尝试运行示例应用程序,并仔细阅读代码,以查看简单的RecyclerViewFastScroller小部件的简单用法。有关github的信息,但我会在这里列出垂直快速滚动条的基本用法。

有关完整示例,请参阅sample application in the repo

基本用法

在RecyclerView所在的活动或片段XML中,包含VerticalRecyclerViewFastScroller对象。以下示例将采用相对布局:

...
  <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"
    />
...

在以编程方式设置布局的片段或活动中,将快速卷轴连接到回收站:

...
  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;
  }
...

希望这有帮助!

编辑:现在增加了对Android-Lollipop-Contacts风格的部分指标的支持!查看sample application's implementation for details.

答案 1 :(得分:27)

由于所有第三方图书馆都存在问题,我决定收集我能找到的内容(主要来自here),修复所有内容并发布我自己的RecyclerView快速滚动条的POC:

https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller

用法:

  1. 制作一个实现BubbleTextGetter的RecyclerView.Adapter,它在数据中给出一个位置,将返回要在bubble-popup中显示的文本。

  2. 将FastScroller放置在RecyclerView容器的布局中(可能位于右侧区域)。

  3. 自定义FastScroller FastScroller

  4. 一些缺点:

    1. 不支持方向更改,但可能很容易修复。
    2. 不支持其他layoutManagers。只有LinearLayoutManager
    3. 需要API 11及以上版本。
    4. 代码:

      <强> 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);
            }
          }
        }
      

答案 2 :(得分:11)

关于RecyclerView快速滚动/部分索引器,有很多未解答的问题,让我们尝试重新组合并收集我们的意见和信息。

简短回答是: 不,您无法启用快速滚动,因为RecyclerView不包含FastScroller对象以及任何相关的逻辑状态变量。这是因为RecyclerView不是AbsListView

另一方面,实现包含转储版本RecyclerView的{​​{1}}和快速滚动的必要逻辑并非不可能,但我还没有看到任何到目前为止实施这个。

请分享您对此的考虑,或者如果您认为我错了。

答案 3 :(得分:10)

Android支持库26.0.0现在支持fastScrollEnabled

RecyclerView的新fastScrollEnabled布尔标志。

如果启用,则必须设置fastScrollHorizo​​ntalThumbDrawable,fastScrollHorizo​​ntalTrackDrawable,fastScrollVerticalThumbDrawable和fastScrollVerticalTrackDrawable。

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

答案 4 :(得分:3)

您也可以使用A-Z Fastscroll for RecyclerView。它是iOS风格。

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

如何使用它:

  • android.support.v7.widget.RecyclerView替换为com.codecomputerlove.fastscrollrecyclerviewdemo.FastScrollRecyclerView
  • 您的适配器需要实现FastScrollRecyclerViewInterface并覆盖getMapIndex()。该函数应该返回mapIndex。查看calculateIndexesForName()获取有关如何创建它的灵感。一旦创建,就将它传递给构造函数中的适配器。
  • 创建FastScrollRecyclerViewItemDecoration的实例并将其添加到RecyclerView上 FastScrollRecyclerViewItemDecoration decoration = new FastScrollRecyclerViewItemDecoration(this); mRecyclerView.addItemDecoration(decoration);
  • <dimen name="fast_scroll_overlay_text_size">100dp</dimen>添加到您的帐户中 /values/dimens.xml个文件。这是重叠字母的dp大小

答案 5 :(得分:3)

您可以尝试我们的lib:https://github.com/FutureMind/recycler-fast-scroll。它仍处于早期开发阶段,但专门用于处理我们与其他图书馆遇到的平滑性问题。它使用了一点点不同的机制。它也支持水平LayoutManager,并且还将在不久的将来支持多列设置。

编辑:现在有一些简洁的自定义选项。

答案 6 :(得分:3)

FastScroller功能从android库26.0.0添加到 RecyclerView

编译依赖

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

将依赖性添加到project.gradle

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

您的recyclerview.xml文件

    <?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>

答案 7 :(得分:1)

可以使用RecycleView及其LayoutManager来实现滚动条。

例如:computeVerticalScrollExtent()computeVerticalScrollOffset()computeVerticalScrollRange()可以提供始终将垂直滚动滑块定位在正确位置的信息。

这些方法也在LayoutManager中用于委派实际测量。因此,所使用的LayoutManager实现必须支持这些测量。

此外,可以通过覆盖RecyclerView的{​​{3}}来拦截滚动拇指上的拖动触摸。在计算了所需的滚动后,可以调用onInterceptTouchEvent()来更新RecyclerView

答案 8 :(得分:1)

这个新库基于框架启动器的快速滚动器:https://github.com/zhanghai/AndroidFastScroll

Usage

new FastScrollerBuilder(recyclerView).build();

(可选)在适配器中实现PopupTextProvider

答案 9 :(得分:0)

只需启用快速滚动并为滚动条添加拇指,跟踪器,如下所示。

<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" />

答案 10 :(得分:0)

由于仅基于xml,所以android studio中的快速滚动器仍然存在很多错误,因此我开始研究可以实现快速滚动的外部库。你可以得到它 here。易于实现和自定义。