Comment désactiver le défilement RecyclerView?


221

Je ne peux pas désactiver le défilement dans le RecyclerView. J'ai essayé d'appeler rv.setEnabled(false)mais je peux toujours faire défiler.

Comment désactiver le défilement?


1
Quel est l'intérêt d'utiliser RecyclerViewsi vous ne souhaitez pas faire défiler?
CommonsWare

16
@CommonsWare, je veux juste le désactiver temporairement, par exemple, pendant que je fais une animation personnalisée avec l'un de ses enfants.
Zsolt Safrany

1
Ah, OK, cela a du sens. J'aurais probablement mis un transparent Viewsur le dessus RecyclerView, basculant entre VISIBLEet GONEselon les besoins, mais au départ, votre approche semble raisonnable.
CommonsWare

J'espère que cela vous aidera à trouver une meilleure solution.
Saiteja Parsi

1
@CommonsWare, voici ce dont j'ai besoin, par exemple. Je dois afficher les images dans RecyclerView une par une, sans images partiellement visibles, une seule dans ma fenêtre. Et il y a des flèches à gauche et à droite avec lesquelles l'utilisateur peut naviguer. Selon l'image actuellement affichée (ils sont de différents types), certaines choses en dehors de RecyclerView sont déclenchées. C'est le design que veulent nos clients.
Varvara Kalinina

Réponses:


368

Pour cela, vous devez remplacer le gestionnaire de mise en page de votre vue de recyclage. De cette façon, il ne désactivera que le défilement, aucune des autres fonctionnalités. Vous pourrez toujours gérer les clics ou tout autre événement tactile. Par exemple:-

Original:

 public class CustomGridLayoutManager extends LinearLayoutManager {
 private boolean isScrollEnabled = true;

 public CustomGridLayoutManager(Context context) {
  super(context);
 }

 public void setScrollEnabled(boolean flag) {
  this.isScrollEnabled = flag;
 }

 @Override
 public boolean canScrollVertically() {
  //Similarly you can customize "canScrollHorizontally()" for managing horizontal scroll
  return isScrollEnabled && super.canScrollVertically();
 }
}

Ici, en utilisant l'indicateur "isScrollEnabled", vous pouvez activer / désactiver temporairement la fonctionnalité de défilement de votre vue de recyclage.

Aussi:

Remplacez simplement votre implémentation existante pour désactiver le défilement et autoriser les clics.

 linearLayoutManager = new LinearLayoutManager(context) {
 @Override
 public boolean canScrollVertically() {
  return false;
 }
};

2
Ce devrait être la bonne réponse car elle désactive le défilement tout en me permettant de cliquer.
Jared Burrows

10
La désactivation du défilement sur les LayoutMangers par défaut devrait déjà exister, un utilisateur de l'API ne devrait pas avoir à le faire. Merci pour la réponse!
Martin O'Shea

1
Que faire si je souhaite désactiver le défilement uniquement à partir d'un certain élément? Cela signifie que vous pouvez faire défiler vers le bas (ou vers le haut), mais ensuite être bloqué, jusqu'à ce que je le réactive à nouveau?
développeur Android

2
Cela désactive également smoothScrollToPosition
Mladen Rakonjac

4
vous ajoutez un objet de version kotlin: LinearLayoutManager (this) {override fun canScrollVertically (): Boolean {return false}}
amorenew

149

La vraie réponse est

recyclerView.setNestedScrollingEnabled(false);

Plus d'informations dans la documentation


39
Cela ne désactivera que le défilement imbriqué, pas tout le défilement. En particulier, si votre RecyclerView est dans un ScrollView mais ne remplit pas l'écran, le ScrollView ne défilera pas (le contenu tient dans l'écran), et vous obtiendrez l'interface utilisateur de défilement dans votre RecyclerView (effet de fin de liste lorsque vous essayez de faire défiler par exemple) ) même s'il ne défilera pas lorsqu'il s'agrandira. L'extension du LayoutManager fait vraiment l'affaire.
personne3000

3
@ personne3000 Je pense que recyclerView.setHasFixedSize (true) fait le travail pour cela. Edit: J'utilise également cela en conjonction avec setFillViewport (true), donc je ne sais pas vraiment lequel des deux le corrige.
mDroidd

5
Cela fonctionne bien pour moi, mais mon "RecyclerView" était à l'intérieur d'une disposition de fragment utilisant "ScrollView", j'ai donc dû changer le "ScrollView" par "NestedScrollView", comme décrit ici: stackoverflow.com/a/38088902/618464
educoutinho

1
Rappelez - vous d'utiliser le nom complet de classe pour NestedScrollView: android.support.v4.widget.NestedScrollView, comme décrit ici, par chessdork: stackoverflow.com/questions/37846245/...
gustavoknz

Après avoir passé 4 heures à essayer de comprendre ce qui ne va pas, cela a résolu mon problème. Dans mon cas, j'utilise une vue de recyclage à l'intérieur d'un MotionLayout. Et j'ai seulement ajouté une zone de balayage pour faire glisser vers le haut en ajoutant du mouvement: touchAnchorId = "@ id / swipe_area". Il fonctionne normalement mais se déclenche également lorsque vous touchez les zones vides de recyclerview. Soyez prudent lorsque vous utilisez motionLayout avec d'autres éléments pouvant être glissés.
O. Kumru

73

La RÉELLE RÉELLE réponse est: Pour API 21 et plus:

Aucun code java nécessaire. Vous pouvez définir android:nestedScrollingEnabled="false" en xml:

<android.support.v7.widget.RecyclerView
     android:id="@+id/recycler"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:clipToPadding="true"
     android:nestedScrollingEnabled="false"
     tools:listitem="@layout/adapter_favorite_place">

11
Je pense que cela devrait être la réponse acceptée, sans aucun effort en classe, vous pouvez désactiver le défilement et pourtant le toucher est autorisé dans la mise en page.
VipiN Negi

Que pour les API inférieures à 21 ??
Mouaad Abdelghafour AITALI

@pic pour les API inférieures, vous pouvez utiliser: recyclerView.setNestedScrollingEnabled (false);
Milad Faridnia

1
Réponse acceptée pour moi.
Fernando Torres

1
c'est la réponse la meilleure et la plus simple, qui évite également la pollution inutile du code et les modèles d'extension de classe sans fin.
Raphael C

52

Cette solution de contournement un peu hack mais cela fonctionne; vous pouvez activer / désactiver le défilement dans leRecyclerView .

Il s'agit d'un vide RecyclerView.OnItemTouchListenervolant chaque événement tactile, désactivant ainsi la cible RecyclerView.

public class RecyclerViewDisabler implements RecyclerView.OnItemTouchListener {

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return true;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

En l'utilisant:

RecyclerView rv = ...
RecyclerView.OnItemTouchListener disabler = new RecyclerViewDisabler();

rv.addOnItemTouchListener(disabler);        // disables scolling
// do stuff while scrolling is disabled
rv.removeOnItemTouchListener(disabler);     // scrolling is enabled again 

8
Est-ce que cela désactive également le clic de l'élément?
Jahir Fiquitiva

@JahirFiquitiva Oui. "Ceci est un RecyclerView.OnItemTouchListener vide volant chaque événement tactile"
Max

1
cela désactive également le défilement de la vue parent
Jemshit Iskenderov

Cela désactive également le clic sur l'élément
Muhammad Riyaz

1
Pourquoi utiliser une solution de contournement hackish (avec effets secondaires) quand il existe une solution propre? Remplacez simplement le LayoutManager (voir les autres réponses)
personne3000

37

Cela fonctionne pour moi:

  recyclerView.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
          return true;
      }
  });

11
Cela désactive tout contact.
Jared Burrows

1
Joli tour! Pour réactiver à nouveau, il suffit de réinitialiser le onTouch de onTouchListener pour retourner false
HendraWD

euh, mais il y a un avertissementCustom view 'RecyclerView' has setOnTouchListener called on it but does not override performClick
HendraWD

Si vous recevez le même avertissement que @HendraWD mentionné dans le commentaire ci-dessus, jetez un œil aux réponses à cette question: stackoverflow.com/questions/46135249/…
Nicolás Carrasco

1
Au lieu de renvoyer true, et donc de désactiver toutes sortes d'événements tactiles, au return e.getAction() == MotionEvent.ACTION_MOVE;lieu de return true;cela, seuls les événements scroll / swipe sont annulés.
Toufic Batache

15

Vous pouvez désactiver le défilement en gelant votre RecyclerView.

Geler: recyclerView.setLayoutFrozen(true)

Pour dégeler: recyclerView.setLayoutFrozen(false)


1
Prenez note: les vues enfants ne sont pas mises à jour lorsque RecyclerView est figé. Je dois désactiver le défilement lorsqu'il y a suffisamment d'espace sur l'écran pour afficher tous les éléments. Oh ... Quelle belle API Android pour cette tâche ...
Oleksandr Nos

1
Obsolète, API 28
OhhhThatVarun

13

Créer une classe qui étend la classe RecyclerView

public class NonScrollRecyclerView extends RecyclerView {

    public NonScrollRecyclerView(Context context) {
        super(context);
    }

    public NonScrollRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public NonScrollRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
                Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
        ViewGroup.LayoutParams params = getLayoutParams();
        params.height = getMeasuredHeight();
    }
}

Cela désactivera l'événement de défilement, mais pas les événements de clic

Utilisez ceci dans votre XML, procédez comme suit:

  <com.yourpackage.xyx.NonScrollRecyclerView 
     ...
     ... 
  />

Merci! Tout cela est nécessaire lorsque nous voulons permettre à la vue du recycleur de prendre toute sa hauteur en fonction du contenu sans défilement interne. :)
Rahul Rastogi

Mais si cette vue de recycleur est dans une vue défilante (egScrollView), le gestionnaire de disposition doit remplacer la méthode canScrollVertically () qui en retourne false.
Rahul Rastogi

@RahulRastogi utilise NestedScrollView au lieu de ScrollView
Ashish

Hé @AshishMangave, dites-moi comment l'activer à nouveau par programme
Shruti

@Shruti, nous ne pouvons pas le faire par programme. bcz nous remplaçons directement la méthode onMeasure de recyclerview.vous pouvez essayer pour ce recyclerView.setNestedScrollingEnabled (false). cela vous aidera
Ashish

11

Si vous désactivez uniquement la fonctionnalité de défilement de, RecyclerViewvous pouvez utiliser la setLayoutFrozen(true);méthode de RecyclerView. Mais il ne peut pas être désactivé de l'événement tactile.

your_recyclerView.setLayoutFrozen(true);

cette méthode est déconseillée. pouvez-vous en suggérer un autre?
Aiyaz Parmar

Obsolète, API 28
OhhhThatVarun

9
recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
            // Stop only scrolling.
            return rv.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING;
        }
    });


Cela interrompt le défilement de smoothScrollToPosition () lorsqu'il est touché.
ypresto

C'est la meilleure solution pour moi car elle permet le défilement par programmation mais évite le défilement utilisateur.
Miguel Sesma

7

Il y a une réponse vraiment simple.

LinearLayoutManager lm = new LinearLayoutManager(getContext()) {
                @Override
                public boolean canScrollVertically() {
                    return false;
                }
            };

Le code ci-dessus désactive le défilement vertical de RecyclerView.



6

A écrit une version kotlin:

class NoScrollLinearLayoutManager(context: Context?) : LinearLayoutManager(context) {
    private var scrollable = true

    fun enableScrolling() {
        scrollable = true
    }

    fun disableScrolling() {
        scrollable = false
    }

    override fun canScrollVertically() =
            super.canScrollVertically() && scrollable


    override fun canScrollHorizontally() =
            super.canScrollVertically()

 && scrollable
}

usage:

recyclerView.layoutManager = NoScrollLinearLayoutManager(context)
(recyclerView.layoutManager as NoScrollLinearLayoutManager).disableScrolling()

5

Étendre le LayoutManageret remplacer canScrollHorizontally()etcanScrollVertically() pour désactiver le défilement.

Sachez que l'insertion d'éléments au début ne fera pas automatiquement défiler jusqu'au début, pour contourner cela, faites quelque chose comme:

  private void clampRecyclerViewScroll(final RecyclerView recyclerView)
  {
    recyclerView.getAdapter().registerAdapterDataObserver(new RecyclerView.AdapterDataObserver()
    {
      @Override
      public void onItemRangeInserted(int positionStart, int itemCount)
      {
        super.onItemRangeInserted(positionStart, itemCount);
        // maintain scroll position at top
        if (positionStart == 0)
        {
          RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
          if (layoutManager instanceof GridLayoutManager)
          {
            ((GridLayoutManager) layoutManager).scrollToPositionWithOffset(0, 0);
          }else if(layoutManager instanceof LinearLayoutManager)
          {
            ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(0, 0);
          }
        }
      }
    });
  }

1
cela désactive également smoothScrollToPosition
Mladen Rakonjac

5

Je sais que cela a déjà une réponse acceptée, mais la solution ne prend pas en compte un cas d'utilisation que j'ai rencontré.

J'avais spécifiquement besoin d'un élément d'en-tête qui était toujours cliquable, mais qui désactivait le mécanisme de défilement de RecyclerView. Cela peut être accompli avec le code suivant:

recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
                            @Override
     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
         return e.getAction() == MotionEvent.ACTION_MOVE;
     }

     @Override
     public void onTouchEvent(RecyclerView rv, MotionEvent e) {

     }

     @Override
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
});

Pour une raison quelconque, vous devez cliquer juste à droite, sans bouger pour que cela fonctionne.
Jared Burrows

Bon point, @JaredBurrows. En effet, nous ignorons ACTION_MOVE. Pour que le toucher soit naturel, il y a un certain compromis entre le toucher et un léger mouvement sur l'écran. Malheureusement, je n'ai pas pu résoudre ce problème.
Ryan Simon

Merci, exactement ce dont j'avais besoin!
Étudiant

5

Comme il setLayoutFrozenest déconseillé, vous pouvez désactiver le défilement en gelant votre RecyclerView à l'aide de suppressLayout.

Geler:

recyclerView.suppressLayout(true)

Pour dégeler:

recyclerView.suppressLayout(false)

4

en XML: -

Vous pouvez ajouter

android:nestedScrollingEnabled="false"

dans le fichier XML de mise en page RecyclerView enfant

ou

en Java: -

childRecyclerView.setNestedScrollingEnabled(false);

à votre RecyclerView en code Java.

Utilisation de ViewCompat (Java): -

childRecyclerView.setNestedScrollingEnabled(false);ne fonctionnera que dans android_version> 21 appareils. pour travailler dans tous les appareils, utilisez les éléments suivants

ViewCompat.setNestedScrollingEnabled(childRecyclerView, false);


Excelent, tous les apis couverts, doivent être mis sous tension.
Ricard

3

Pour une raison quelconque, la réponse @Alejandro Gracia ne commence à fonctionner qu'après quelques secondes. J'ai trouvé une solution qui bloque instantanément le RecyclerView:

recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                return true;
            }
            @Override
            public void onTouchEvent(RecyclerView rv, MotionEvent e) {
            }
            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
            }
        });

3

Remplacez onTouchEvent () et onInterceptTouchEvent () et retournez false si vous n'avez pas du tout besoin de OnItemTouchListener. Cela ne désactive pas OnClickListeners de ViewHolders.

public class ScrollDisabledRecyclerView extends RecyclerView {
    public ScrollDisabledRecyclerView(Context context) {
        super(context);
    }

    public ScrollDisabledRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public ScrollDisabledRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        return false;
    }
}

3

Vous pouvez ajouter cette ligne après avoir configuré votre adaptateur

ViewCompat.setNestedScrollingEnabled(recyclerView, false);

Votre vue de recyclage fonctionnera maintenant avec un défilement fluide


2

Je me bats dans ce problème depuis une heure, donc je voudrais partager mon expérience, pour la solution layoutManager, c'est bien mais si vous voulez réactiver le défilement, le recycleur reviendra en haut.

La meilleure solution jusqu'à présent (pour moi du moins) utilise la méthode @Zsolt Safrany mais en ajoutant getter et setter afin que vous n'ayez pas à supprimer ou à ajouter OnItemTouchListener.

Comme suit

public class RecyclerViewDisabler implements RecyclerView.OnItemTouchListener {

    boolean isEnable = true;

    public RecyclerViewDisabler(boolean isEnable) {
        this.isEnable = isEnable;
    }

    public boolean isEnable() {
        return isEnable;
    }

    public void setEnable(boolean enable) {
        isEnable = enable;
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return !isEnable;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {}

   @Override
   public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept){}
 }

Usage

RecyclerViewDisabler disabler = new RecyclerViewDisabler(true);
feedsRecycler.addOnItemTouchListener(disabler);

// TO ENABLE/DISABLE JUST USE THIS
disabler.setEnable(enable);

2

Dans Kotlin, si vous ne voulez pas créer une classe supplémentaire juste pour définir une valeur, vous pouvez créer une classe anonyme à partir de LayoutManager:

recyclerView.layoutManager = object : LinearLayoutManager(context) {
    override fun canScrollVertically(): Boolean = false
}

1

Voici comment je l'ai fait avec la liaison de données:

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clipChildren="false"
                android:onTouch="@{(v,e) -> true}"/>

Au lieu du «vrai», j'ai utilisé une variable booléenne qui a changé en fonction d'une condition afin que la vue du recycleur bascule entre être désactivée et activée.


1

Je suis tombé sur un fragment qui contient plusieurs RecycleView, donc je n'ai besoin que d'une barre de défilement au lieu d'une barre de défilement dans chaque RecycleView.

Je viens donc de mettre le ScrollView dans le conteneur parent qui contient les 2 RecycleViews et utiliser android:isScrollContainer="false" dans le RecycleView

<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutManager="LinearLayoutManager"
    android:isScrollContainer="false" />

1

Ajouter

android:descendantFocusability="blocksDescendants"

dans votre enfant de SrollView ou NestedScrollView (et parent de ListView, recyclerview et gridview)


1

Il existe un moyen plus simple de désactiver le défilement (techniquement, il s'agit plutôt d'intercepter un événement de défilement et de le terminer lorsqu'une condition est remplie), en utilisant uniquement des fonctionnalités standard. RecyclerViewa la méthode appelée addOnScrollListener(OnScrollListener listener), et en utilisant juste cela, vous pouvez l'empêcher de défiler, juste pour:

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (viewModel.isItemSelected) {
            recyclerView.stopScroll();
        }
    }
});

Cas d'utilisation: disons que vous souhaitez désactiver le défilement lorsque vous cliquez sur l'un des éléments à l'intérieur RecyclerViewafin de pouvoir effectuer certaines actions avec lui, sans être distrait par un défilement accidentel vers un autre élément, et lorsque vous avez terminé, cliquez simplement sur l'élément à nouveau pour activer le défilement. Pour cela, vous souhaitez attacher OnClickListenerà chaque élément à l'intérieur RecyclerView, donc lorsque vous cliquez sur un élément, il basculera isItemSelectedde falseà true. De cette façon, lorsque vous essayez de faire défiler, RecyclerViewappellera automatiquement la méthode onScrollStateChangedet depuis qu'il est isItemSelecteddéfini sur true, il s'arrêtera immédiatement, avant d' RecyclerViewavoir la chance, bien ... de faire défiler.

Remarque: pour une meilleure convivialité, essayez d'utiliser GestureListenerplutôt que d' OnClickListenerempêcher les accidentalclics.


stopScroll()n'est pas de geler le défilement, c'est juste d'arrêter / d'arrêter le défilement recyclerview.
Farid

@FARID, oui, cette méthode arrête tout défilement en cours, et dans ce cas particulier, elle le fait à chaque fois qu'un utilisateur essaie de faire défiler le contenu de RecyclerView avec la valeur isItemSelectedset true. Sinon, vous auriez besoin d'un RecyclerView personnalisé pour désactiver le défilement pour de bon et je voulais l'éviter car j'avais besoin d'un peu de fonctionnalités supplémentaires, que cette solution fournit.
Tim Jorjev

0

Ajoutez simplement ceci à votre recycleview en xml

 android:nestedScrollingEnabled="false"

comme ça

<android.support.v7.widget.RecyclerView
                    android:background="#ffffff"
                    android:id="@+id/myrecycle"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:nestedScrollingEnabled="false">

0

Pour arrêter le défilement au toucher mais continuer à faire défiler via les commandes:

if (appTopBarMessagesRV == null) {appTopBarMessagesRV = findViewById (R.id.mainBarScrollMessagesRV);

        appTopBarMessagesRV.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

                if ( rv.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING)
                {
                     // Stop  scrolling by touch

                    return false;
                }
                return  true;
            }
        });
    }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.