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?

Zsolt Safrany
la source
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;
 }
};
Saurabh Garg
la source
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

Bozic Nebojsa
la source
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">
Milad Faridnia
la source
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 
Zsolt Safrany
la source
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;
      }
  });
alxgarcia
la source
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)

Virat Singh
la source
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 
     ...
     ... 
  />
Ashish
la source
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);
Ekta Bhawsar
la source
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;
        }
    });

Rajesh
la source
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.

Emi Raz
la source
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()
Je suis un dragon grenouille
la source
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);
          }
        }
      }
    });
  }
Aidanvii
la source
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) {

    }
});
Ryan Simon
la source
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)
Shylendra Madda
la source
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);

jafarbtech
la source
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) {
            }
        });
vovahost
la source
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;
    }
}
ypresto
la source
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

Atefeh Srch
la source
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);
Aladdin
la source
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
}
Micer
la source
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.

bwhite
la source
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" />
pk028382
la source
1

Ajouter

android:descendantFocusability="blocksDescendants"

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

Pankaj Talaviya
la source
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.

Tim Jorjev
la source
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">
anas
la source
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;
            }
        });
    }
Gars
la source