RecyclerView dans ScrollView ne fonctionne pas

J’essaie d’implémenter une disposition qui contient RecyclerView et ScrollView à la même disposition.

Modèle de mise en page:

  ...    

Problèmes: je peux faire défiler jusqu’au dernier élément de ScrollView

Les choses que j’ai essayées:

  1. vue carte dans ScrollView (maintenant ScrollView contient RecyclerView ) – peut voir la carte jusqu’à la RecyclerView
  2. pensée initiale était d’implémenter ce viewGroup utilisant RecyclerView au lieu de ScrollView où l’un de ses types de vues est le CardView mais j’ai eu exactement les mêmes résultats qu’avec le ScrollView

utiliser NestedScrollView au lieu de ScrollView

Veuillez consulter le document de référence NestedScrollView pour plus d’informations.

et ajoutez recyclerView.setNestedScrollingEnabled(false); à votre RecyclerView

Je sais que je suis en retard sur le jeu, mais le problème persiste même après que Google ait corrigé le problème sur android.support.v7.widget.RecyclerView

Le problème que je reçois maintenant est RecyclerView avec layout_height=wrap_content ne prenant pas la hauteur de tous les problèmes d’éléments dans ScrollView qui ne se produit que sur les versions Marshmallow et Nougat + (API 23, 24, 25).
(UPDATE: le remplacement de ScrollView par android.support.v4.widget.NestedScrollView fonctionne sur toutes les versions. J’ai en quelque sorte omis de tester la solution acceptée . Ajoutée dans mon projet github en tant que démo.)

Après avoir essayé différentes choses, j’ai trouvé une solution palliative à ce problème.

Voici ma structure en quelques mots:

   (vertical - this is the only child of scrollview)   (layout_height=wrap_content)  

La solution de contournement est le wrap le RecyclerView avec RelativeLayout . Ne me demande pas comment j’ai trouvé cette solution de contournement !!! ¯\_(ツ)_/¯

    

Un exemple complet est disponible sur le projet GitHubhttps://github.com/amardeshbd/android-recycler-view-wrap-content

Voici un screencast de démonstration montrant le correctif en action:

Screencast

Bien que la recommandation

vous ne devriez jamais mettre une vue déroulante dans une autre vue déroulante

Est un bon conseil, mais si vous définissez une hauteur fixe sur la vue du recycleur, cela devrait fonctionner correctement.

Si vous connaissez la hauteur de la disposition de l’élément d’adaptateur, vous pouvez simplement calculer la hauteur de RecyclerView.

 int viewHeight = adapterItemSize * adapterData.size(); recyclerView.getLayoutParams().height = viewHeight; 

La nouvelle bibliothèque de support Android 23.2 résout ce problème, vous pouvez maintenant définir wrap_content comme la hauteur de votre RecyclerView et fonctionne correctement.

Bibliothèque de support Android 23.2

Dans le cas où la hauteur fixe pour RecyclerView ne fonctionnait pas pour quelqu’un (comme moi), voici ce que j’ai ajouté à la solution de hauteur fixe:

 mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { int action = e.getAction(); switch (action) { case MotionEvent.ACTION_MOVE: rv.getParent().requestDisallowInterceptTouchEvent(true); break; } return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } }); 

RecyclerViews peuvent être placées dans ScrollViews à condition qu’elles ne défilent pas elles-mêmes. Dans ce cas, il est logique de faire une hauteur fixe.

La solution appropriée consiste à utiliser wrap_content sur la hauteur RecyclerView, puis à implémenter un LinearLayoutManager personnalisé capable de gérer correctement le retour à la ligne.

Copiez ce LinearLayoutManager dans votre projet: https://github.com/serso/android-linear-layout-manager/blob/master/lib/src/main/java/org/solovyev/android/views/llm/LinearLayoutManager.java

Ensuite, enveloppez le RecyclerView:

  

Et le configurer comme ça:

  RecyclerView list = (RecyclerView)findViewById(R.id.list); list.setHasFixedSize(true); list.setLayoutManager(new com.example.myapp.LinearLayoutManager(list.getContext())); list.setAdapter(new MyViewAdapter(data)); 

Edit: Cela peut entraîner des complications avec le défilement car RecyclerView peut voler les événements tactiles de ScrollView. Ma solution consistait simplement à abandonner le RecyclerView en totalité et à utiliser LinearLayout, à gonfler par programme les sous-vues et à les append à la présentation.

Pour ScrollView , vous pouvez utiliser fillViewport=true et make layout_height="match_parent" comme ci-dessous et placer la vue recycleur à l’intérieur:

    

Aucun autre réglage de la hauteur n’est nécessaire via le code.

Calculer manuellement la hauteur de RecyclerView n’est pas bon, mieux vaut utiliser un LayoutManager personnalisé.

La raison du problème ci-dessus est que toute vue dont le défilement est ListView ( ListView , GridView , RecyclerView ) n’a pas réussi à calculer sa hauteur lorsque l’ajout d’un enfant dans une autre vue a défilé. Ainsi, outrepasser sa méthode onMeasure résoudra le problème.

Veuillez remplacer le gestionnaire de disposition par défaut par les éléments suivants:

 public class MyLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager { private static boolean canMakeInsetsDirty = true; private static Field insetsDirtyField = null; private static final int CHILD_WIDTH = 0; private static final int CHILD_HEIGHT = 1; private static final int DEFAULT_CHILD_SIZE = 100; private final int[] childDimensions = new int[2]; private final RecyclerView view; private int childSize = DEFAULT_CHILD_SIZE; private boolean hasChildSize; private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS; private final Rect tmpRect = new Rect(); @SuppressWarnings("UnusedDeclaration") public MyLinearLayoutManager(Context context) { super(context); this.view = null; } @SuppressWarnings("UnusedDeclaration") public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); this.view = null; } @SuppressWarnings("UnusedDeclaration") public MyLinearLayoutManager(RecyclerView view) { super(view.getContext()); this.view = view; this.overScrollMode = ViewCompat.getOverScrollMode(view); } @SuppressWarnings("UnusedDeclaration") public MyLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) { super(view.getContext(), orientation, reverseLayout); this.view = view; this.overScrollMode = ViewCompat.getOverScrollMode(view); } public void setOverScrollMode(int overScrollMode) { if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER) throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode); if (this.view == null) throw new IllegalStateException("view == null"); this.overScrollMode = overScrollMode; ViewCompat.setOverScrollMode(view, overScrollMode); } public static int makeUnspecifiedSpec() { return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); } @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { final int widthMode = View.MeasureSpec.getMode(widthSpec); final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED; final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED; final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY; final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY; final int unspecified = makeUnspecifiedSpec(); if (exactWidth && exactHeight) { // in case of exact calculations for both dimensions let's use default "onMeasure" implementation super.onMeasure(recycler, state, widthSpec, heightSpec); return; } final boolean vertical = getOrientation() == VERTICAL; initChildDimensions(widthSize, heightSize, vertical); int width = 0; int height = 0; // it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This // happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the // recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never // called whiles scrolling) recycler.clear(); final int stateItemCount = state.getItemCount(); final int adapterItemCount = getItemCount(); // adapter always contains actual data while state might contain old data (fe data before the animation is // done). As we want to measure the view with actual data we must use data from the adapter and not from the // state for (int i = 0; i < adapterItemCount; i++) { if (vertical) { if (!hasChildSize) { if (i < stateItemCount) { // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items // we will use previously calculated dimensions measureChild(recycler, i, widthSize, unspecified, childDimensions); } else { logMeasureWarning(i); } } height += childDimensions[CHILD_HEIGHT]; if (i == 0) { width = childDimensions[CHILD_WIDTH]; } if (hasHeightSize && height >= heightSize) { break; } } else { if (!hasChildSize) { if (i < stateItemCount) { // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items // we will use previously calculated dimensions measureChild(recycler, i, unspecified, heightSize, childDimensions); } else { logMeasureWarning(i); } } width += childDimensions[CHILD_WIDTH]; if (i == 0) { height = childDimensions[CHILD_HEIGHT]; } if (hasWidthSize && width >= widthSize) { break; } } } if (exactWidth) { width = widthSize; } else { width += getPaddingLeft() + getPaddingRight(); if (hasWidthSize) { width = Math.min(width, widthSize); } } if (exactHeight) { height = heightSize; } else { height += getPaddingTop() + getPaddingBottom(); if (hasHeightSize) { height = Math.min(height, heightSize); } } setMeasuredDimension(width, height); if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) { final boolean fit = (vertical && (!hasHeightSize || height < heightSize)) || (!vertical && (!hasWidthSize || width < widthSize)); ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS); } } private void logMeasureWarning(int child) { if (BuildConfig.DEBUG) { Log.w("MyLinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." + "To remove this message either use #setChildSize() method or don't run RecyclerView animations"); } } private void initChildDimensions(int width, int height, boolean vertical) { if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) { // already initialized, skipping return; } if (vertical) { childDimensions[CHILD_WIDTH] = width; childDimensions[CHILD_HEIGHT] = childSize; } else { childDimensions[CHILD_WIDTH] = childSize; childDimensions[CHILD_HEIGHT] = height; } } @Override public void setOrientation(int orientation) { // might be called before the constructor of this class is called //noinspection ConstantConditions if (childDimensions != null) { if (getOrientation() != orientation) { childDimensions[CHILD_WIDTH] = 0; childDimensions[CHILD_HEIGHT] = 0; } } super.setOrientation(orientation); } public void clearChildSize() { hasChildSize = false; setChildSize(DEFAULT_CHILD_SIZE); } public void setChildSize(int childSize) { hasChildSize = true; if (this.childSize != childSize) { this.childSize = childSize; requestLayout(); } } private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) { final View child; try { child = recycler.getViewForPosition(position); } catch (IndexOutOfBoundsException e) { if (BuildConfig.DEBUG) { Log.w("MyLinearLayoutManager", "MyLinearLayoutManager doesn't work well with animations. Consider switching them off", e); } return; } final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams(); final int hPadding = getPaddingLeft() + getPaddingRight(); final int vPadding = getPaddingTop() + getPaddingBottom(); final int hMargin = p.leftMargin + p.rightMargin; final int vMargin = p.topMargin + p.bottomMargin; // we must make insets dirty in order calculateItemDecorationsForChild to work makeInsetsDirty(p); // this method should be called before any getXxxDecorationXxx() methods calculateItemDecorationsForChild(child, tmpRect); final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child); final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child); final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally()); final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically()); child.measure(childWidthSpec, childHeightSpec); dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin; dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin; // as view is recycled let's not keep old measured values makeInsetsDirty(p); recycler.recycleView(child); } private static void makeInsetsDirty(RecyclerView.LayoutParams p) { if (!canMakeInsetsDirty) { return; } try { if (insetsDirtyField == null) { insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty"); insetsDirtyField.setAccessible(true); } insetsDirtyField.set(p, true); } catch (NoSuchFieldException e) { onMakeInsertDirtyFailed(); } catch (IllegalAccessException e) { onMakeInsertDirtyFailed(); } } private static void onMakeInsertDirtyFailed() { canMakeInsetsDirty = false; if (BuildConfig.DEBUG) { Log.w("MyLinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect"); } } } 

Essaye ça. Réponse très tardive Mais sûrement aider quelqu’un à l’avenir.

Définissez votre Scrollview sur NestedScrollView

     

Dans votre recyclerview

 recyclerView.setNestedScrollingEnabled(false); recyclerView.setHasFixedSize(false); 

UPDATE: cette réponse est datée maintenant car il existe des widgets tels que NestedScrollView et RecyclerView qui prennent en charge le défilement nested.

vous ne devriez jamais mettre une vue déroulante dans une autre vue déroulante!

Je vous suggère de créer votre vue principale de recycleur de mise en page et de placer vos vues en tant qu’éléments de la vue recycleur.

jetez un oeil à cet exemple, il montre comment utiliser plusieurs vues à l’intérieur de l’adaptateur de vue du recycleur. lien vers l’exemple

Vous pouvez utiliser cette façon soit:

Ajoutez cette ligne à votre vue recyclerView xml:

  android:nestedScrollingEnabled="false" 

essayez-le, recyclerview sera défiler en douceur avec une hauteur flexible

J’espère que cela a aidé.

Il semble que NestedScrollView résout le problème. J’ai testé en utilisant cette mise en page:

        

Et ça marche sans problèmes

J’avais le même problème. C’est ce que j’ai essayé et ça marche. Je partage mon code xml et java. J’espère que cela aidera quelqu’un.

Voici le xml

  

           

Voici le code Java associé. Il fonctionne comme un charme.

 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); recyclerView.setLayoutManager(linearLayoutManager); recyclerView.setNestedScrollingEnabled(false); 

Cela fait le tour:

 recyclerView.setNestedScrollingEnabled(false); 

En fait, le but principal de RecyclerView est de compenser ListView et ScrollView . Au lieu de faire ce que vous êtes en train de faire: Avoir un RecyclerView dans un ScrollView , je suggérerais d’avoir seulement un RecyclerView capable de gérer plusieurs types d’enfants.

Vous devez d’abord utiliser NestedScrollView au lieu de ScrollView et placer le RecyclerView dans NestedScrollView .

Utilisez la classe de disposition personnalisée pour mesurer la hauteur et la largeur de l’écran:

 public class CustomLinearLayoutManager extends LinearLayoutManager { public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } private int[] mMeasuredDimension = new int[2]; @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { final int widthMode = View.MeasureSpec.getMode(widthSpec); final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); int width = 0; int height = 0; for (int i = 0; i < getItemCount(); i++) { if (getOrientation() == HORIZONTAL) { measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), heightSpec, mMeasuredDimension); width = width + mMeasuredDimension[0]; if (i == 0) { height = mMeasuredDimension[1]; } } else { measureScrapChild(recycler, i, widthSpec, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); height = height + mMeasuredDimension[1]; if (i == 0) { width = mMeasuredDimension[0]; } } } switch (widthMode) { case View.MeasureSpec.EXACTLY: width = widthSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } switch (heightMode) { case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } setMeasuredDimension(width, height); } private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.getViewForPosition(position); recycler.bindViewToPosition(view, position); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), p.width); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height); view.measure(childWidthSpec, childHeightSpec); measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin; measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; recycler.recycleView(view); } } } 

Et implémenter le code ci-dessous dans l'activité / fragment de RecyclerView :

  final CustomLinearLayoutManager layoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(mAdapter); recyclerView.setNestedScrollingEnabled(false); // Disables scrolling for RecyclerView, CustomLinearLayoutManager used instead of MyLinearLayoutManager recyclerView.setHasFixedSize(false); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int visibleItemCount = layoutManager.getChildCount(); int totalItemCount = layoutManager.getItemCount(); int lastVisibleItemPos = layoutManager.findLastVisibleItemPosition(); Log.i("getChildCount", Ssortingng.valueOf(visibleItemCount)); Log.i("getItemCount", Ssortingng.valueOf(totalItemCount)); Log.i("lastVisibleItemPos", Ssortingng.valueOf(lastVisibleItemPos)); if ((visibleItemCount + lastVisibleItemPos) >= totalItemCount) { Log.i("LOG", "Last Item Reached!"); } } }); 

Si RecyclerView n’affiche qu’une seule ligne dans ScrollView. Il vous suffit de définir la hauteur de votre ligne sur android:layout_height="wrap_content" .

Vous pouvez également remplacer LinearLayoutManager pour que recyclerview se déroule en douceur

 @Override public boolean canScrollVertically(){ return false; } 

Désolé d’être en retard à la fête, mais il semble qu’il y ait une autre solution qui fonctionne parfaitement pour l’affaire que vous avez mentionnée.

Si vous utilisez une vue recycleur dans une vue recycleur, elle semble fonctionner parfaitement. Je l’ai personnellement essayé et utilisé, et il ne semble pas y avoir de lenteur et de saccade. Maintenant, je ne suis pas sûr que ce soit une bonne pratique ou non, mais l’imbrication de plusieurs vues de recycleur, même la vue de défilement nestede, ralentit. Mais cela semble bien fonctionner. S’il vous plaît essayer. Je suis sûr que la nidification va parfaitement se passer avec ça.

** Solution qui a fonctionné pour moi
Utilisez NestedScrollView avec height comme wrap_content

 
RecyclerView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" tools:targetApi="lollipop"

and view holder layout
android:layout_width="match_parent"
android:layout_height="wrap_content"

// Votre contenu de ligne va ici