Как заставить WRAP_CONTENT работать с RecyclerView

У меня есть DialogFragment который содержит RecyclerView (список карт).

Внутри этого RecyclerView есть один или несколько CardViews которые могут иметь любую высоту.

Я хочу дать этому DialogFragment правильную высоту, основанную на CardViews , которые содержатся внутри.

Обычно это было бы просто, я бы установил wrap_content в RecyclerView таким образом.

   

Поскольку я использую RecyclerView это не работает:

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

а также

Высота вложенного ресайклера не обтекает его содержимое

На обеих этих страницах люди предлагают расширить LinearLayoutManager и переопределить onMeasure()

Я сначала использовал LayoutManager, который кто-то предоставил в первой ссылке:

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

Однако это не сработало, потому что

heightSize = View.MeasureSpec.getSize(heightSpec);

возвращает очень большое значение, которое, как представляется, связано с match_parent .

Комментируя height = heightSize; (во втором случае коммутатора) мне удалось сделать работу с высотой, но только если ребенок TextView внутри CardView не CardView свой собственный текст (длинное предложение).

Как только этот TextView обертывает собственный текст, высота ДОЛЖНА увеличиваться, но это не так. Он рассчитал высоту этого длинного предложения как одну строку, а не завернутую линию (2 или более).

Любые советы о том, как я должен улучшить этот LayoutManager чтобы мой RecyclerView работал с WRAP_CONTENT ?

Изменить: этот менеджер макетов может работать для большинства людей, но он все еще имеет проблемы с прокруткой и вычислением высот обертывания текстовых просмотров

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

Из обновления Android Support 23.2.1 все WRAP_CONTENT должны работать правильно.

Обновите версию библиотеки в файле gradle OR или далее:

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

решили некоторые проблемы, такие как исправленные ошибки, связанные с различными методами измерения

Проверьте http://developer.android.com/tools/support-library/features.html#v7-recyclerview

вы можете проверить историю изменений в библиотеке поддержки

Вот утонченная версия classа, которая, похоже, работает и не имеет проблем, которые могут возникнуть в других решениях:

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

Это также доступно как библиотека . Ссылка на соответствующий class .

ОБНОВИТЬ

В обновлении Android Support Library 23.2 все WRAP_CONTENT должны работать правильно.

Обновите версию библиотеки в файле gradle.

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

Оригинальный ответ

Как ответ на другой вопрос, вам нужно использовать оригинальный метод onMeasure (), когда высота просмотра вашего ресайклера больше высоты экрана. Этот менеджер компоновки может вычислять ItemDecoration и может прокручиваться с большим количеством.

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

оригинальный ответ: https://stackoverflow.com/a/28510031/1577792

Вот версия c # для mono android

 /* * 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 * distributed under the License is distributed 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 } } } 

Наконец нашел решение этой проблемы.

Все, что вам нужно сделать, это обернуть RecyclerView в RelativeLayout . Возможно, есть и другие виды, которые также могут работать.

    

RecyclerView добавил поддержку wrap_content в 23.2.0 которая была ошибкой, 23.2.1 была просто стабильной, поэтому вы можете использовать:

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

Вы можете увидеть историю изменений здесь:

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

Заметка:

Также обратите внимание, что после обновления библиотеки поддержки RecyclerView будет уважать wrap_content а также match_parent поэтому, если у вас есть Item View для RecyclerView установленного как match_parent одно представление заполнит весь экран

Проблема с прокруткой и wrap_content текста заключается в том, что этот код предполагает, что и ширина, и высота установлены на wrap_content . Однако LayoutManager должен знать, что горизонтальная ширина ограничена. Поэтому вместо того, чтобы создавать свой собственный widthSpec для каждого дочернего представления, просто используйте оригинальную widthSpec :

 @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); } } 

Попробуйте это (это неприятное решение, но оно может работать): в методе onCreate вашей Activity или в методе onViewCreated вашего fragmentа. Установите обратный вызов, готовый к запуску при первом рендеринге RecyclerView , например:

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

В calculeRecyclerViewFullHeight вычисляет полную высоту RecyclerView основанную на высоте его дочерних элементов.

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

В моем случае мой RecyclerView содержится в SwipeRefreshLayout , поэтому я устанавливаю высоту в SwipeRefreshView а не RecyclerView но если у вас нет SwipeRefreshView вы можете установить высоту в RecyclerView .

Дайте мне знать, помогло вам это или нет.

Теперь это работает, поскольку они выпустили версию в версии 23.2, как указано в этом сообщении . Цитирование официального блога

Этот выпуск приносит новую функциональность API LayoutManager: автоматическое измерение! Это позволяет RecyclerView самостоятельно определять размер содержимого. Это означает, что ранее недоступные сценарии, такие как использование WRAP_CONTENT для измерения RecyclerView, теперь возможны. Вы найдете, что все встроенные LayoutManager теперь поддерживают автоматическое измерение.

Вместо того, чтобы использовать любую библиотеку, самое простое решение до выхода новой версии – просто открыть b.android.com/74772 . Вы легко найдете лучшее решение, известное на сегодняшний день.

PS: b.android.com/74772#c50 работал для меня

Я использовал некоторые из вышеперечисленных решений, но работал на width но height .

  1. Если ваша указанная compileSdkVersion больше 23 , вы можете напрямую использовать RecyclerView, предоставленный в их соответствующих библиотеках поддержки просмотра recycler, например, для 23 это будет 'com.android.support:recyclerview-v7:23.2.1' . Эти библиотеки поддержки поддерживают атрибуты wrap_content как по ширине, так и по высоте.

Вы должны добавить его в свои зависимости

 compile 'com.android.support:recyclerview-v7:23.2.1' 
  1. Если ваша compileSdkVersion меньше 23 , вы можете использовать нижеуказанное решение.

Я нашел эту тему в Google по этой проблеме. В этом streamе есть один вклад, который приводит к реализации LinearLayoutManager .

Я тестировал его как по высоте, так и по ширине, и он работал отлично для меня в обоих случаях.

 /* * 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 * distributed under the License is distributed 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"); } } }

Этот комментарий помог мне. 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.

Замените measureScrapChild чтобы выполнить код:

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

Я использую xamarin, так что это код c #. Я думаю, что это можно легко «перевести» на Java.

Обновите представление с нулевым значением вместо родительской группы просмотра в окне просмотра адаптера onCreateViewHolder.

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

Вы должны поместить FrameLayout в качестве основного представления, а затем поместить в RelativeLayout с помощью ScrollView и, по крайней мере, в ваш RecyclerView, он работает для меня.

Настоящий трюк здесь RelativeLayout …

Рад был помочь.

я предлагаю вам переусердствовать в любом другом макете (предпочтительный вариант размещения). Затем измените высоту / ширину recyclerview как родительский элемент соответствия на этот макет и установите высоту / ширину родительского макета как содержимое обертки. меня устраивает

Я не работал над своим ответом, но так, как я знаю, StaggridLayoutManager нет. сетки 1 может решить вашу проблему, так как StaggridLayout автоматически отрегулирует ее высоту и ширину по размеру содержимого. если он работает, не забудьте проверить его как правильный ответ. Спасибо.

Давайте будем гением компьютера.