【问题标题】:How to add a fast-scroller to the RecyclerView如何将快速滚动条添加到 RecyclerView
【发布时间】:2015-03-04 02:14:49
【问题描述】:

背景

在 ListView 上,您可以拥有一个快速滚动器,它允许您拖动滚动条以轻松滚动到您希望的任何位置(使用 fastScrollEnabled 属性)

与“SectionIndexer”类和可选的一些属性一起,您可以在使用此滚动条时显示一个漂亮的弹出窗口(链接here)。

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

问题

RecyclerView 似乎没有这些。甚至没有快速滚动。

问题

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

【问题讨论】:

    标签: android scrollbar android-recyclerview fastscroll sectionindexer


    【解决方案1】:

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

    github.com/danoz73/RecyclerViewFastScroller

    尝试运行示例应用程序,并仔细阅读代码以查看简单 RecyclerViewFastScroller 小部件的相当简单的用法。 github上有资料,不过这里我会介绍一下垂直快速滚动条的基本用法。

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

    基本用法

    在 RecyclerView 所在的 Activity 或片段 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.

    【讨论】:

    • 不错。虽然不喜欢物品的颜色:)。它可以以某种方式处理 SectionIndexer 吗?例如,要在 Lollipop 的联系人应用程序上显示当前字母的气泡?
    • 本周我将添加类似的其他实现!只是想先展示一个基本的例子:)
    • 好的,我会给你奖励,但我仍然想看到这些功能...... :) 也很高兴看到水平滚动,让它看起来像棒棒糖上的材料设计。
    • 我已经更新了项目以包含一个可以与 SectionIndexer 交互的 SectionIndicator。详情请查看 github。
    • 观察到滚动不流畅。它在跳跃。
    【解决方案2】:

    新答案: 随着时间的推移,我注意到我的原始答案与其他解决方案相比存在一些缺点,尤其是对于 ViewPager 中的片段。

    我建议您使用android-x solution 以防您不需要气泡,或者使用第三方库(here 是一个不错的)以防您需要。


    旧答案:

    由于所有第三方库都有问题,我决定收集我能找到的东西(主要来自 here),修复所有问题并发布我自己的 RecyclerView 快速滚动器的 POC:

    https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller

    用法:

    1. 创建一个实现 BubbleTextGetter 的 RecyclerView.Adapter,它在数据中给定一个位置将返回文本以显示在气泡弹出窗口中。

    2. 将 FastScroller 放置在包含 RecyclerView 的布局内(可能位于右侧区域)。

    3. 自定义 FastScroller FastScroller

    一些缺点:

    1. 不支持方向更改,但可能很容易修复。
    2. 不支持其他布局管理器。只有 LinearLayoutManager
    3. 需要 API 11 及更高版本。

    代码:

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

    【讨论】:

    • 如果您的视图大小不同怎么办?这就是我现在的问题。我知道什么观点会改变。但不确定我是否可以用它来帮助我。我有几个高度加倍的视图。
    • 是的,但这有很多帮助。这只是我弄清楚如何补偿这些偏移量的问题。
    • @LoyalRayne 请使用 Github 存储库。这里可能没有更新。
    • 为什么我的 FastScroller 不包含所有回收站视图?我按照你的所有步骤..但它没有正确显示,请帮助我@androiddeveloper
    • @MicheleLacorte 似乎我忘了添加导入。立即尝试。
    【解决方案3】:

    Android 支持库 26.0.0 现在支持fastScrollEnabled

    RecyclerView 的新 fastScrollEnabled 布尔标志。

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

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

    【讨论】:

    • 有趣!那么如何在拖动时显示带有文本的气泡呢?
    • 只是提到那些drawables必须是StateListDrawable的,你还必须为两个轴设置drawables。
    • 我刚刚实现 fastScroll 使用支持库 26.0.1,它只显示快速滚动条,必须自定义显示气泡文本
    • drawable必须是StateListDrawable是什么意思?
    • @Shrikant statelist drawable 是当你实现一个带有state_pressed="true" 的项目、带有state_selected="true" 的项目以及selector 中的普通项目时。所以你将在选择器中有 3 个项目。
    【解决方案4】:

    关于RecyclerViewfast-scroll/section indexer 有很多未解答的问题,让我们在这里尝试重新组合并收集我们的意见和信息。

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

    另一方面,实现RecyclerView 也不是不可能的,它包含FastScroller 的转储版本和快速滚动的必要逻辑,但到目前为止我还没有看到任何实现。

    如果您认为我错了,请分享您对此的看法。

    【讨论】:

    • 这是个好主意。希望我有时间自己尝试并实施。
    • RecyclerView 不适合与 FastScroller 一起使用。这与它的无限列表的理念和设计理念背道而驰。但是没有什么可以阻止人们尝试的:-)
    • 请删除您的答案,因为它完全错误会让所有人感到困惑
    【解决方案5】:

    您也可以将 A-Z Fastscroll 用于 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);
    • &lt;dimen name="fast_scroll_overlay_text_size"&gt;100dp&lt;/dimen&gt; 添加到您的 /values/dimens.xml 文件。这是叠加字母的 dp 大小

    【讨论】:

    • 这个其实不错,不过是IOS风格的。快速滚动条适用于 Android。
    【解决方案6】:

    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>
    

    【讨论】:

    • 缺少一些资源:line_dark、line_dark、dark_grey。你在哪里找到这些信息的?有关于它的教程/文章吗?什么时候添加的(时间)?
    • @androiddeveloper ,我忘记更改一些元素名称(我把和我的代码一样),我没有找到这个信息,上面的代码是我自己项目的一部分,我目前正在工作。我认为这是很好的分享信息,我是如何解决这个问题的。
    • 那你是怎么知道的呢?能否请您更新一下,以便我试用?
    • @androiddeveloper upvote 这里是文档链接developer.android.com/topic/libraries/support-library/…
    【解决方案7】:

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

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

    【讨论】:

    • RecyclerView 适用于 API 7 及更高版本。为什么你需要 API 15 来获得一个简单的视图?此外,它还有一个错误:使用快速滚动条滚动到某处,然后正常滚动。 scoller 将从其当前位置跳跃。
    • 原因可能和你的repo一样。我可以立即将其更改为 11,但当我有时间将动画移植到 7 时,我会将其更改为 7。感谢您注意到这个错误,我必须在添加水平滚动功能后引入它
    • 对不起。我不记得我当时做了什么。但我确实计划让它在 pre-API 11 上运行。
    • 这个支持开箱即用的StaggeredGridLayoutManager
    【解决方案8】:

    提供了使用RecycleView 及其LayoutManager 实现滚动条的规定。

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

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

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

    【讨论】:

    • 你能用代码演示一下吗?我问这个是为了真正找到一种放置快速滚动条的方法,而且由于我是 RecyclerView 的新手,在我短暂的空闲时间里很难做到这一点
    【解决方案9】:

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

    Usage:

    new FastScrollerBuilder(recyclerView).build();
    

    可选择在适配器中实现PopupTextProvider

    【讨论】:

    • 酷!谢谢。可能有用
    【解决方案10】:

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

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

    【讨论】:

    • 是的,这个问题在这件事成为可能之前就被问到了。我想知道它的可定制性如何。是否可以设置气泡的外观?甚至有可能出现泡沫吗?如何设置气泡中显示的内容?
    【解决方案11】:

    android studio 中的fast-scroller 仍然有很多bug,因为它只基于xml,所以我去挖掘可以实现快速滚动的外部库。你可以得到它 here。非常容易实现和定制。

    【讨论】:

    • 看起来不错,但似乎对规则过于严格。要求我们知道每个项目的高度,并且每个项目类型的高度也相同......
    猜你喜欢
    • 2015-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多