Как добавить быстрый скроллер в RecyclerView

Фон

В ListView у вас может быть быстрая прокрутка, которая позволяет перетаскивать полосу прокрутки, чтобы легко прокручивать туда, куда вы хотите (используя атрибут fastScrollEnabled)

Вместе с классом " SectionIndexer " и, возможно, некоторыми атрибутами, у вас может появиться приятное всплывающее окно, которое отображается при использовании этой полосы прокрутки (ссылка здесь).

Такая вещь отображается в приложении контактов, чтобы вы могли легко прокручивать до конкретных букв.

Эта проблема

RecyclerView, похоже, не имеет ни одного из них. Даже не быстрая прокрутка.

Вопрос

Как добавить функциональность быстрой прокрутки для RecyclerView?

9 ответов

Решение

Поскольку у всех сторонних библиотек были проблемы, я решил собрать то, что я могу найти (в основном отсюда), исправить все и опубликовать свой собственный POC быстрой прокрутки RecyclerView:

https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller

использование:

  1. создайте RecyclerView.Adapter, который реализует BubbleTextGetter, который с учетом позиции в данных вернет текст, отображаемый в всплывающем окне.

  2. Поместите FastScroller внутри макета, который содержит RecyclerView (возможно, в правой области).

  3. Настройка FastScroller FastScroller

Некоторые недостатки:

  1. не поддерживает изменение ориентации, но, вероятно, это легко исправить.
  2. не поддерживает других layoutManager. Только 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>

Основная деятельность

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

Я наткнулся на этот вопрос несколько дней назад, когда столкнулся с этой ситуацией. Вот мой пример реализации FastScroll для RecyclerView:

https://github.com/danoz73/RecyclerViewFastScroller

Попробуйте запустить пример приложения и просмотреть код, чтобы увидеть довольно простое использование простого виджета RecyclerViewFastScroller. Есть информация о github, но я включу здесь основное использование вертикальной быстрой прокрутки.

Полный пример приведен в примере приложения в репо.

Основное использование

В XML-код активности или фрагмента, в котором находится RecyclerView, включите объект 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! Проверьте реализацию примера приложения для деталей.

Библиотека поддержки Android 26.0.0 теперь поддерживает fastScrollEnabled

Новый логический флаг fastScrollEnabled для RecyclerView.

Если включено, должны быть установлены fastScrollHor horizontalThumbDrawable, fastScrollHor horizontalTrackDrawable, fastScrollVerticalThumbDrawable и fastScrollVerticalTrackDrawable.

Образец - https://android.jlelse.eu/fast-scrolling-with-recyclerview-2b89d4574688

Есть много вопросов без ответа о RecyclerView и быстрая прокрутка / индексатор разделов, давайте попробуем перегруппировать и собрать наши мнения и информацию здесь.

Краткий ответ: НЕТ, вы не можете включить быструю прокрутку, потому что RecyclerView не содержит ни объекта FastScroller, ни связанных с ним переменных логического состояния. Это потому что RecyclerView не является AbsListView.

С другой стороны, это не невозможно реализовать RecyclerView который содержит дамп версию FastScroller и необходимая логика для быстрой прокрутки, но я не видел никакой реализации этого до сих пор.

Пожалуйста, поделитесь своим мнением об этом, или если вы думаете, что я неправ.

Функциональность 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>

Вы также можете использовать AZ Fastscroll для RecyclerView. Это стиль iOS.

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

Как это использовать:

  • замещать android.support.v7.widget.RecyclerView с com.codecomputerlove.fastscrollrecyclerviewdemo.FastScrollRecyclerView
  • Ваш адаптер должен реализовать FastScrollRecyclerViewInterface и переопределить getMapIndex(), Функция должна возвращать mapIndex. Заглянуть в calculateIndexesForName() для вдохновения о том, как его создать. После создания передайте его адаптеру в конструкторе.
  • Создать экземпляр FastScrollRecyclerViewItemDecoration и добавьте его в свой RecyclerViewFastScrollRecyclerViewItemDecoration decoration = new FastScrollRecyclerViewItemDecoration(this); mRecyclerView.addItemDecoration(decoration);
  • добавлять <dimen name="fast_scroll_overlay_text_size">100dp</dimen> на ваш/values/dimens.xml файл. Это размер dp наложенной буквы

Вы можете попробовать нашу библиотеку: https://github.com/FutureMind/recycler-fast-scroll. Он все еще находится на ранней стадии разработки, но был создан специально для решения проблемы гладкости, с которой мы столкнулись в других библиотеках. Он использует немного другой механизм. Он также поддерживает горизонтальный LayoutManager и в ближайшем будущем также будет поддерживать настройки нескольких столбцов.

Изменить: теперь есть несколько опций настройки.

Эта новая библиотека была основана на быстром скроллере средства запуска фреймворка: https://github.com/zhanghai/AndroidFastScroll

Использование:

new FastScrollerBuilder(recyclerView).build();

При желании реализовать PopupTextProvider в адаптере.

Существует возможность реализации полос прокрутки с RecycleView И его LayoutManager,

Например: computeVerticalScrollExtent(), computeVerticalScrollOffset() а также computeVerticalScrollRange() может предоставить информацию для того, чтобы всегда располагать вертикальный большой палец в нужном месте.

Эти методы также есть в LayoutManager для делегирования фактических измерений. Итак LayoutManager используемая реализация должна поддерживать эти измерения.

Кроме того, перетаскивание на большой палец прокрутки может быть перехвачено путем переопределения onInterceptTouchEvent() из RecyclerView, И после расчета желаемого прокрутки, scrollTo() можно вызвать для обновления RecyclerView,

Быстрая прокрутка в студии Android по-прежнему очень глючна, поскольку основана только на xml, поэтому я стал искать внешние библиотеки, которые могли бы реализовать быструю прокрутку. Вы можете получить это здесь. Очень легко реализовать и настроить.

Просто включите быструю прокрутку и добавьте большой палец, трекер для полосы прокрутки, как показано ниже.

<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" />
Другие вопросы по тегам