Comment puis-je faire fonctionner WRAP_CONTENT sur un RecyclerView

J’ai un DialogFragment qui contient un RecyclerView (une liste de cartes).

Dans ce RecyclerView trouve une ou plusieurs CardViews qui peuvent avoir n’importe quelle hauteur.

Je veux donner à ce DialogFragment la hauteur correcte basée sur les CardViews contenus dans.

Normalement, ce serait simple, je définirais wrap_content sur RecyclerView comme ceci.

   

Parce que j’utilise un RecyclerView cela ne fonctionne pas voir:

https://issuetracker.google.com/issues/37001674

et

La hauteur de la vue Recycler nestede ne recouvre pas son contenu

Sur ces deux pages, les gens suggèrent d’étendre LinearLayoutManager et de remplacer onMeasure()

J’ai d’abord utilisé le LayoutManager que quelqu’un a fourni dans le premier lien:

 public static class WrappingLayoutManager extends LinearLayoutManager { public WrappingLayoutManager(Context context) { super(context); } 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); measureScrapChild(recycler, 0, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); int width = mMeasuredDimension[0]; int height = mMeasuredDimension[1]; switch (widthMode) { case View.MeasureSpec.EXACTLY: case View.MeasureSpec.AT_MOST: width = widthSize; break; case View.MeasureSpec.UNSPECIFIED: } switch (heightMode) { case View.MeasureSpec.EXACTLY: case View.MeasureSpec.AT_MOST: height = heightSize; break; 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); 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(); measuredDimension[1] = view.getMeasuredHeight(); recycler.recycleView(view); } } } 

Cependant, cela n’a pas fonctionné parce que

heightSize = View.MeasureSpec.getSize(heightSpec);

renvoie une très grande valeur qui semble être liée à match_parent .

En commentant height = heightSize; (dans le deuxième cas de commutation), j’ai réussi à faire fonctionner la hauteur, mais uniquement si un enfant TextView à l’intérieur de CardView ne remplit pas son propre texte (une longue phrase).

Dès que ce TextView enveloppe son propre texte, la hauteur DEVRAIT augmenter mais ce n’est pas le cas. Il a calculé la hauteur de cette longue phrase sous la forme d’une seule ligne et non d’une ligne (2 ou plus).

Des conseils sur la manière d’améliorer ce LayoutManager afin que mon RecyclerView fonctionne avec WRAP_CONTENT ?

Modifier: ce gestionnaire de disposition peut fonctionner pour la plupart des utilisateurs, mais il pose toujours des problèmes de défilement et de calcul de la hauteur des vues de texte

 public class MyLinearLayoutManager extends LinearLayoutManager { public MyLinearLayoutManager(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++) { measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); if (getOrientation() == HORIZONTAL) { width = width + mMeasuredDimension[0]; if (i == 0) { height = mMeasuredDimension[1]; } } else { 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); 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); } } } 

A partir de la mise à jour 23.2.1 d’Android Support Library , tous les WRAP_CONTENT doivent fonctionner correctement.

Veuillez mettre à jour la version d’une bibliothèque dans le fichier de gradle OU :

 comstack 'com.android.support:recyclerview-v7:23.2.1' 

résolu un problème comme les bogues corrigés liés à diverses méthodes de spécification de mesure

Vérifiez http://developer.android.com/tools/support-library/features.html#v7-recyclerview

vous pouvez vérifier l’ historique de révision de la bibliothèque de support

Voici la version raffinée de la classe qui semble fonctionner et manque de problèmes. D’autres solutions ont:

 package org.solovyev.android.views.llm; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; /** * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters. * * Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be. * If animations are not used at all then a normal measuring procedure will run and child views will be measured during * the measure pass. */ public class LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager { 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 int childSize = DEFAULT_CHILD_SIZE; private boolean hasChildSize; @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(Context context) { super(context); } @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } 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 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, widthSpec, unspecified, childDimensions); } else { logMeasureWarning(i); } } height += childDimensions[CHILD_HEIGHT]; if (i == 0) { width = childDimensions[CHILD_WIDTH]; } if (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, heightSpec, childDimensions); } else { logMeasureWarning(i); } } width += childDimensions[CHILD_WIDTH]; if (i == 0) { height = childDimensions[CHILD_HEIGHT]; } if (width >= widthSize) { break; } } } if ((vertical && height < heightSize) || (!vertical && width < widthSize)) { // we really should wrap the contents of the view, let's do it if (exactWidth) { width = widthSize; } else { width += getPaddingLeft() + getPaddingRight(); } if (exactHeight) { height = heightSize; } else { height += getPaddingTop() + getPaddingBottom(); } setMeasuredDimension(width, height); } else { // if calculated height/width exceeds requested height/width let's use default "onMeasure" implementation super.onMeasure(recycler, state, widthSpec, heightSpec); } } private void logMeasureWarning(int child) { if (BuildConfig.DEBUG) { Log.w("LinearLayoutManager", "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 widthSpec, int heightSpec, int[] dimensions) { final View child = recycler.getViewForPosition(position); 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; final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child); final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child); final int childWidthSpec = getChildMeasureSpec(widthSpec, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally()); final int childHeightSpec = getChildMeasureSpec(heightSpec, 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; recycler.recycleView(child); } } 

Ceci est également disponible en tant que bibliothèque . Lien vers la classe pertinente .

METTRE À JOUR

Par la mise à jour 23.2 d’Android Support Library, tous les WRAP_CONTENT devraient fonctionner correctement.

Veuillez mettre à jour la version d’une bibliothèque dans le fichier gradle.

 comstack 'com.android.support:recyclerview-v7:23.2.0' 

Réponse originale

En réponse à une autre question, vous devez utiliser la méthode originale onMeasure () lorsque la hauteur de la vue du recycleur est supérieure à la hauteur de l’écran. Ce gestionnaire de disposition peut calculer ItemDecoration et peut faire défiler avec plus.

  public class MyLinearLayoutManager extends LinearLayoutManager { public MyLinearLayoutManager(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++) { measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); if (getOrientation() == HORIZONTAL) { width = width + mMeasuredDimension[0]; if (i == 0) { height = mMeasuredDimension[1]; } } else { height = height + mMeasuredDimension[1]; if (i == 0) { width = mMeasuredDimension[0]; } } } // If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view. if (height < heightSize && width < widthSize) { 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); } else { super.onMeasure(recycler, state, widthSpec, heightSpec); } } private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.getViewForPosition(position); // For adding Item Decor Insets to view super.measureChildWithMargins(view, 0, 0); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view) , p.height); view.measure(childWidthSpec, childHeightSpec); // Get decorated measurements measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin; measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin; recycler.recycleView(view); } } } 

réponse originale: https://stackoverflow.com/a/28510031/1577792

Voici la version c # pour Android mono

 /* * Ported by Jagadeesh Govindaraj (@jaganjan) *Copyright 2015 serso aka se.solovyev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * dissortingbuted under the License is dissortingbuted on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contact details * * Email: se.solovyev @gmail.com * Site: http://se.solovyev.org */ using Android.Content; using Android.Graphics; using Android.Support.V4.View; using Android.Support.V7.Widget; using Android.Util; using Android.Views; using Java.Lang; using Java.Lang.Reflect; using System; using Math = Java.Lang.Math; namespace Droid.Helper { public class WrapLayoutManager : LinearLayoutManager { private const int DefaultChildSize = 100; private static readonly Rect TmpRect = new Rect(); private int _childSize = DefaultChildSize; private static bool _canMakeInsetsDirty = true; private static readonly int[] ChildDimensions = new int[2]; private const int ChildHeight = 1; private const int ChildWidth = 0; private static bool _hasChildSize; private static Field InsetsDirtyField = null; private static int _overScrollMode = ViewCompat.OverScrollAlways; private static RecyclerView _view; public WrapLayoutManager(Context context, int orientation, bool reverseLayout) : base(context, orientation, reverseLayout) { _view = null; } public WrapLayoutManager(Context context) : base(context) { _view = null; } public WrapLayoutManager(RecyclerView view) : base(view.Context) { _view = view; _overScrollMode = ViewCompat.GetOverScrollMode(view); } public WrapLayoutManager(RecyclerView view, int orientation, bool reverseLayout) : base(view.Context, orientation, reverseLayout) { _view = view; _overScrollMode = ViewCompat.GetOverScrollMode(view); } public void SetOverScrollMode(int overScrollMode) { if (overScrollMode < ViewCompat.OverScrollAlways || overScrollMode > ViewCompat.OverScrollNever) throw new ArgumentException("Unknown overscroll mode: " + overScrollMode); if (_view == null) throw new ArgumentNullException(nameof(_view)); _overScrollMode = overScrollMode; ViewCompat.SetOverScrollMode(_view, overScrollMode); } public static int MakeUnspecifiedSpec() { return View.MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified); } public override void OnMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { var widthMode = View.MeasureSpec.GetMode(widthSpec); var heightMode = View.MeasureSpec.GetMode(heightSpec); var widthSize = View.MeasureSpec.GetSize(widthSpec); var heightSize = View.MeasureSpec.GetSize(heightSpec); var hasWidthSize = widthMode != MeasureSpecMode.Unspecified; var hasHeightSize = heightMode != MeasureSpecMode.Unspecified; var exactWidth = widthMode == MeasureSpecMode.Exactly; var exactHeight = heightMode == MeasureSpecMode.Exactly; var unspecified = MakeUnspecifiedSpec(); if (exactWidth && exactHeight) { // in case of exact calculations for both dimensions let's use default "onMeasure" implementation base.OnMeasure(recycler, state, widthSpec, heightSpec); return; } var vertical = Orientation == Vertical; InitChildDimensions(widthSize, heightSize, vertical); var width = 0; var 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(); var stateItemCount = state.ItemCount; var adapterItemCount = ItemCount; // 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 (var 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[ChildHeight]; if (i == 0) { width = ChildDimensions[ChildWidth]; } 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[ChildWidth]; if (i == 0) { height = ChildDimensions[ChildHeight]; } if (hasWidthSize && width >= widthSize) { break; } } } if (exactWidth) { width = widthSize; } else { width += PaddingLeft + PaddingRight; if (hasWidthSize) { width = Math.Min(width, widthSize); } } if (exactHeight) { height = heightSize; } else { height += PaddingTop + PaddingBottom; if (hasHeightSize) { height = Math.Min(height, heightSize); } } SetMeasuredDimension(width, height); if (_view == null || _overScrollMode != ViewCompat.OverScrollIfContentScrolls) return; var fit = (vertical && (!hasHeightSize || height < heightSize)) || (!vertical && (!hasWidthSize || width < widthSize)); ViewCompat.SetOverScrollMode(_view, fit ? ViewCompat.OverScrollNever : ViewCompat.OverScrollAlways); } private void LogMeasureWarning(int child) { #if DEBUG Log.WriteLine(LogPriority.Warn, "LinearLayoutManager", "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"); #endif } private void InitChildDimensions(int width, int height, bool vertical) { if (ChildDimensions[ChildWidth] != 0 || ChildDimensions[ChildHeight] != 0) { // already initialized, skipping return; } if (vertical) { ChildDimensions[ChildWidth] = width; ChildDimensions[ChildHeight] = _childSize; } else { ChildDimensions[ChildWidth] = _childSize; ChildDimensions[ChildHeight] = height; } } public void ClearChildSize() { _hasChildSize = false; SetChildSize(DefaultChildSize); } public void SetChildSize(int size) { _hasChildSize = true; if (_childSize == size) return; _childSize = size; RequestLayout(); } private void MeasureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) { View child = null; try { child = recycler.GetViewForPosition(position); } catch (IndexOutOfRangeException e) { Log.WriteLine(LogPriority.Warn, "LinearLayoutManager", "LinearLayoutManager doesn't work well with animations. Consider switching them off", e); } if (child != null) { var p = child.LayoutParameters.JavaCast() var hPadding = PaddingLeft + PaddingRight; var vPadding = PaddingTop + PaddingBottom; var hMargin = p.LeftMargin + p.RightMargin; var 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); var hDecoration = GetRightDecorationWidth(child) + GetLeftDecorationWidth(child); var vDecoration = GetTopDecorationHeight(child) + GetBottomDecorationHeight(child); var childWidthSpec = GetChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.Width, CanScrollHorizontally()); var childHeightSpec = GetChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.Height, CanScrollVertically()); child.Measure(childWidthSpec, childHeightSpec); dimensions[ChildWidth] = GetDecoratedMeasuredWidth(child) + p.LeftMargin + p.RightMargin; dimensions[ChildHeight] = 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) { var klass = Java.Lang.Class.FromType (typeof (RecyclerView.LayoutParams)); InsetsDirtyField = klass.GetDeclaredField("mInsetsDirty"); InsetsDirtyField.Accessible = true; } InsetsDirtyField.Set(p, true); } catch (NoSuchFieldException e) { OnMakeInsertDirtyFailed(); } catch (IllegalAccessException e) { OnMakeInsertDirtyFailed(); } } private static void OnMakeInsertDirtyFailed() { _canMakeInsetsDirty = false; #if DEBUG Log.Warn("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect"); #endif } } } 

Enfin trouvé la solution à ce problème.

Tout ce que vous avez à faire est d’emballer le RecyclerView dans un RelativeLayout . Peut-être qu’il y a d’autres vues qui peuvent aussi fonctionner.

    

RecyclerView ajouté le support pour wrap_content dans 23.2.0 qui était buggé, 23.2.1 était juste stable, vous pouvez donc utiliser:

 comstack 'com.android.support:recyclerview-v7:24.2.0' 

Vous pouvez voir l’historique des révisions ici:

https://developer.android.com/topic/libraries/support-library/revisions.html

Remarque:

Notez également qu’après la mise à jour de la bibliothèque de support, RecyclerView respectera wrap_content et match_parent donc si vous avez une vue Item d’un object RecyclerView en tant que match_parent la vue unique remplira tout l’écran.

Le problème avec le défilement et le retour à la ligne est que ce code suppose que la largeur et la hauteur sont définies sur wrap_content . Cependant, le LayoutManager doit savoir que la largeur horizontale est contrainte. Ainsi, au lieu de créer votre propre widthSpec pour chaque vue enfant, utilisez simplement la widthSpec origine:

 @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++) { measureScrapChild(recycler, i, widthSpec, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); if (getOrientation() == HORIZONTAL) { width = width + mMeasuredDimension[0]; if (i == 0) { height = mMeasuredDimension[1]; } } else { 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); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height); view.measure(widthSpec, childHeightSpec); measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin; measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; recycler.recycleView(view); } } 

Essayez ceci (c’est une mauvaise solution, mais ça peut marcher): dans la méthode onCreate de votre Activity ou dans la méthode onViewCreated de votre fragment. Définissez un rappel prêt à être déclenché lors du premier rendu de RecyclerView , comme ceci:

 vRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { calculeRecyclerViewFullHeight(); } }); 

Dans calculeRecyclerViewFullHeight calculez la hauteur totale de RecyclerView fonction de la taille de ses enfants.

 protected void calculateSwipeRefreshFullHeight() { int height = 0; for (int idx = 0; idx < getRecyclerView().getChildCount(); idx++ ) { View v = getRecyclerView().getChildAt(idx); height += v.getHeight(); } SwipeRefreshLayout.LayoutParams params = getSwipeRefresh().getLayoutParams(); params.height = height; getSwipeRefresh().setLayoutParams(params); } 

Dans mon cas, ma RecyclerView contient un SwipeRefreshLayout pour cette raison. Je mets la hauteur à SwipeRefreshView et non à RecyclerView mais si vous n'avez pas de SwipeRefreshView vous pouvez définir la hauteur à la place de RecyclerView .

Faites-moi savoir si cela vous a aidé ou non.

Cela fonctionne maintenant comme ils ont fait une version dans la version 23.2, comme indiqué dans cet article . Citant le blogpost officiel

Cette version apporte une nouvelle fonctionnalité intéressante à l’API LayoutManager: la mesure automatique! Cela permet à un RecyclerView de se dimensionner en fonction de la taille de son contenu. Cela signifie que des scénarios précédemment indisponibles, tels que l’utilisation de WRAP_CONTENT pour une dimension de RecyclerView, sont désormais possibles. Vous trouverez que tous les LayoutManagers intégrés prennent désormais en charge la mesure automatique.

Au lieu d’utiliser une bibliothèque, la solution la plus simple jusqu’à la sortie de la nouvelle version est d’ouvrir b.android.com/74772 . Vous trouverez facilement la meilleure solution connue à ce jour.

PS: b.android.com/74772#c50 a fonctionné pour moi

J’avais utilisé certaines des solutions ci-dessus mais cela fonctionnait pour la width mais la height .

  1. Si votre comstackSdkVersion spécifié comstackSdkVersion supérieur à 23 , vous pouvez directement utiliser RecyclerView fourni dans leurs bibliothèques de support respectives pour la vue recycleur, par exemple, il sera 'com.android.support:recyclerview-v7:23.2.1' . Ces bibliothèques de support prennent en charge les atsortingbuts de wrap_content pour la largeur et la hauteur.

Vous devez l’append à vos dépendances

 comstack 'com.android.support:recyclerview-v7:23.2.1' 
  1. Si votre comstackSdkVersion inférieure à 23 , vous pouvez utiliser la solution mentionnée ci-dessous.

J’ai trouvé ce fil de Google concernant ce problème. Dans cette discussion, il y a une consortingbution qui mène à l’implémentation de LinearLayoutManager .

Je l’ai testé à la fois en hauteur et en largeur et cela a bien fonctionné pour moi dans les deux cas.

 /* * Copyright 2015 serso aka se.solovyev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * dissortingbuted under the License is dissortingbuted on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contact details * * Email: [email protected] * Site: http://se.solovyev.org */ package org.solovyev.android.views.llm; import android.content.Context; import android.graphics.Rect; import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import java.lang.reflect.Field; /** * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters. * 

* Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be. * If animations are not used at all then a normal measuring procedure will run and child views will be measured during * the measure pass. */ public class LinearLayoutManager 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 LinearLayoutManager(Context context) { super(context); this.view = null; } @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); this.view = null; } @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(RecyclerView view) { super(view.getContext()); this.view = view; this.overScrollMode = ViewCompat.getOverScrollMode(view); } @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(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("LinearLayoutManager", "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("LinearLayoutManager", "LinearLayoutManager 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("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect"); } } }

Ce commentaire m’a aidé. Put the recyclerview in any other layout (Relative layout is preferable). Then change recyclerview's height/width as match parent to that layout and set the parent layout's height/width as wrap content.

Remplacez measureScrapChild pour suivre le code:

 private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.GetViewForPosition(position); if (view != null) { MeasureChildWithMargins(view, widthSpec, heightSpec); measuredDimension[0] = view.MeasuredWidth; measuredDimension[1] = view.MeasuredHeight; recycler.RecycleView(view); } } 

J’utilise xamarin, c’est donc du code c #. Je pense que cela peut être facilement “traduit” en Java.

Mettez à jour votre vue avec la valeur NULL à la place de la vue parent dans la méthode ViewChatViewHolder.

 @Override public AdapterItemSku.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = inflator.inflate(R.layout.layout_item, null, false); return new MyViewHolder(view); } 

Vous devez mettre une vue FrameLayout en tant que vue principale, puis la placer dans un RelativeLayout avec ScrollView et au moins votre RecyclerView, cela fonctionne pour moi.

Le vrai truc est le RelativeLayout …

Heureux d’aider.

Je vous suggère de placer la recyclée dans une autre mise en page (la mise en page relative est préférable). Modifiez ensuite la hauteur / largeur de recyclerview en tant que parent correspondant à cette disposition et définissez la hauteur / largeur de la mise en page parente en tant que contenu de retour à la ligne. ça marche pour moi

Je n’ai pas travaillé sur ma réponse mais la façon dont je le connais StaggridLayoutManager sans. de la grid 1 peut résoudre votre problème car StaggridLayout ajustera automatiquement sa hauteur et sa largeur sur la taille du contenu. si cela fonctionne, n’oubliez pas de le vérifier comme une bonne réponse.