web-dev-qa-db-fra.com

Comment ajouter un défilement rapide au RecyclerView

Contexte

Sur ListView, vous pouvez avoir un défilement rapide qui vous permet de faire glisser une barre de défilement pour faire défiler facilement où vous le souhaitez (en utilisant fastScrollEnabled attribut)

En association avec la classe " SectionIndexer " et éventuellement avec certains attributs, vous pouvez obtenir un Nice popup qui s'affiche lorsque vous utilisez cette barre de défilement (link here ).

Une telle chose est affichée sur l'application Contacts afin que vous puissiez facilement faire défiler des lettres spécifiques.

Le problème

RecyclerView ne semble pas en avoir. Pas même un défilement rapide.

La question

Comment ajouter une fonctionnalité de défilement rapide à RecyclerView?

68
android developer

Toutes les bibliothèques tierces ayant des problèmes, j'ai décidé de rassembler tout ce que je pouvais trouver (principalement de ici ), de tout réparer et de publier mon propre POC du défilement rapide de RecyclerView:

https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller

usage:

  1. créez un objet RecyclerView.Adapter qui implémente BubbleTextGetter, qui donne une position dans les données retournera le texte à afficher dans le popup.

  2. positionnez le FastScroller dans la présentation contenant le RecyclerView (probablement à la bonne zone).

  3. Personnaliser le FastScroller FastScroller 

Quelques inconvénients:

  1. ne supporte pas le changement d'orientation, mais c'est probablement facile à corriger.
  2. ne prend pas en charge les autres gestionnaires de mise en page. Seulement LinearLayoutManager 
  3. Nécessite l'API 11 et plus.

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>

Activité principale

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

Je suis tombé sur cette question il y a quelques jours lorsque je me suis retrouvé dans cette situation. Voici mon exemple d'implémentation de FastScroll for RecyclerView

github.com/danoz73/RecyclerViewFastScroller

Essayez d’exécuter l’exemple d’application et parcourez le code pour voir une utilisation assez simple d’un simple widget RecyclerViewFastScroller. Il y a des informations sur github, mais je vais inclure l'utilisation de base ici pour un scroller rapide vertical.

Pour un exemple complet, reportez-vous à l'application exemple dans le référentiel .

Usage basique

Dans l'activité XML ou le fragment XML où réside votre RecyclerView, incluez un objet VerticalRecyclerViewFastScroller. L'exemple suivant serait dans une disposition relative:

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

Dans votre fragment ou activité où vous avez configuré la mise en page par programme, connectez le défilement rapide au recycleur:

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

J'espère que cela t'aides!

EDIT: Ajout de la prise en charge des indicateurs de section de style Android-Lollipop! Découvrez l'implémentation de l'application exemple pour plus de détails.

52
Daniel Smith

La bibliothèque de support Android 26.0.0 prend désormais en charge fastScrollEnabled

Nouvel indicateur booléen fastScrollEnabled pour RecyclerView. 

Si cette option est activée, les options fastScrollHorizontalThumbDrawable, fastScrollHorizontalTrackDrawable, fastScrollVerticalThumbDrawable et fastScrollVerticalTrackDrawable doivent être définies.

Exemple - https://Android.jlelse.eu/fast-scrolling-with-recyclerview-2b89d4574688

10

Vous pouvez également utiliser A-Z Fastscroll pour RecyclerView. C'est le style iOS.

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

Comment l'utiliser:

  • Remplacez Android.support.v7.widget.RecyclerView par com.codecomputerlove.fastscrollrecyclerviewdemo.FastScrollRecyclerView
  • Votre adaptateur doit implémenter FastScrollRecyclerViewInterface et remplacer getMapIndex(). La fonction devrait renvoyer le mapIndex. Regardez dans calculateIndexesForName() pour trouver l'inspiration sur la façon de le créer. Une fois créé, transmettez-le à l'adaptateur du constructeur.
  • Créez une instance de FastScrollRecyclerViewItemDecoration et ajoutez-la sur votre RecyclerView FastScrollRecyclerViewItemDecoration decoration = new FastScrollRecyclerViewItemDecoration(this); mRecyclerView.addItemDecoration(decoration);
  • ajoutez <dimen name="fast_scroll_overlay_text_size">100dp</dimen> à votre fichier /values/dimens.xml. C'est la taille en dp de la lettre superposée
3
Flavius Mester

La fonctionnalité FastScroller est ajoutée à partir de la bibliothèque Android 26.0.0 pour RecyclerView  

compiler la dépendance

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

ajouter la dépendance à project.gradle

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

votre fichier 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>
3
Abhiode

Vous pouvez essayer notre lib: https://github.com/FutureMind/recycler-fast-scroll . Il en est encore à ses débuts, mais il a été conçu spécifiquement pour traiter le problème de manque de cohérence rencontré avec d'autres bibliothèques. Il utilise un mécanisme un peu différent. Il supporte également LayoutManager horizontal et prendra également en charge les configurations multi-colonnes dans un proche avenir. 

Edit: il y a quelques options de personnalisation soignées maintenant.

3
Michał K

Il est prévu de mettre en œuvre des barres de défilement avec RecycleView et sa LayoutManager

Par exemple: computeVerticalScrollExtent() , computeVerticalScrollOffset() et computeVerticalScrollRange() peut fournir des informations permettant de toujours positionner un curseur de défilement vertical au bon endroit. 

Ces méthodes existent également dans LayoutManager pour la délégation des mesures réelles. Donc, l'implémentation LayoutManager utilisée doit supporter ces mesures.

En outre, faites glisser le doigt sur le curseur de défilement peut être intercepté en remplaçant onInterceptTouchEvent() of RecyclerView. Et après avoir calculé le parchemin souhaité, scrollTo() peut être appelé pour mettre à jour RecyclerView

1
S.D.

Il suffit d’activer le défilement rapide et d’ajouter le pouce, suivi de la barre de défilement comme ci-dessous.

<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" />
0
Maya Mohite