我如何使WRAP_CONTENT在RecyclerView上工作
我有一个包含RecyclerView
(卡片列表)的DialogFragment
。
在这个RecyclerView
是一个或多个CardViews
可以有任何高度。
我想给这个DialogFragment
正确的高度基于CardViews
包含在内。
通常这会很简单,我会像这样在RecyclerView
上设置wrap_content
。
<android.support.v7.widget.RecyclerView ... xmlns:tools="http://schemas.android.com/tools" android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="true" android:scrollbars="vertical" > </android.support.v7.widget.RecyclerView>
因为我正在使用RecyclerView
这不起作用,请参阅:
https://issuetracker.google.com/issues/37001674
和
嵌套的Recycler视图高度不包裹其内容
在这两个页面上,人们build议扩展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;
(在第二个开关的情况下)我设法使高度工作,但只有当CardView
内的TextView
子不包装自己的文本(长句)。
只要TextView
包装它自己的文本的高度应该增加,但它不。 它将长句子的高度计算为单行,而不是包线(2或更多)。
任何build议,我应该如何改进这个LayoutManager
所以我的RecyclerView
与WRAP_CONTENT
一起WRAP_CONTENT
?
编辑:这个布局pipe理器可能适用于大多数人,但它仍然有滚动和计算包装文字高度的问题
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); } } }
更新(2016年3月)
通过Android支持库23.2.1更新,所有WRAP_CONTENT都应该正常工作。
请在gradle
文件中更新库的版本或进一步:
compile 'com.android.support:recyclerview-v7:23.2.1'
解决了一些问题,如修正了与各种度量规格方法有关的bug
检查http://developer.android.com/tools/support-library/features.html#v7-recyclerview
您可以查看支持库修订历史logging
这里是这个类的精炼版本,似乎工作,并没有其他解决scheme的问题:
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); } }
这也可以作为一个图书馆 。 链接到相关课程
UPDATE
通过Android支持库23.2更新,所有WRAP_CONTENT应该正常工作。
请在gradle文件中更新库的版本。
compile 'com.android.support:recyclerview-v7:23.2.0'
原始答复
就其他问题回答,当您的回收站视图高度大于屏幕高度时,您需要使用原始的onMeasure()方法。 这个布局pipe理器可以计算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
这是单声道的Android的C#版本
/* * 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<RecyclerView.LayoutParams>() 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
在23.2.0
增加了对wrap_content
支持,这是bug,23.2.1刚刚稳定,所以你可以使用:
compile 'com.android.support:recyclerview-v7:24.2.0'
您可以在这里看到修订历史logging:
https://developer.android.com/topic/libraries/support-library/revisions.html
注意:
另外请注意,在更新支持库之后, RecyclerView
将尊重wrap_content
以及match_parent
所以如果您将RecyclerView
的Item View设置为match_parent
则单个视图将填充整个屏幕
滚动和文本包装的问题是,这段代码假设宽度和高度都设置为wrap_content
。 但是, LayoutManager
需要知道水平宽度是受限制的。 因此,不要为每个子视图创build自己的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); } }
尝试这个(这是一个讨厌的解决scheme,但它可能工作):在您的Activity
的onCreate
方法或您的片段的onViewCreated
方法。 设置callback准备好在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版本中发布的那样,正如本文所述。 引用官方的博文
此版本为LayoutManager API带来了令人兴奋的新function:自动测量! 这允许RecyclerView根据其内容的大小自行resize。 这意味着以前不可用的scheme,例如对RecyclerView的维度使用WRAP_CONTENT,现在是可能的。 您会发现所有内置的LayoutManagers现在都支持自动测量。
直到新版本出来,最简单的解决scheme是打开b.android.com/74772 。 您可以轻松find迄今为止已知的最佳解决scheme。
PS: b.android.com/74772#c50为我工作
我已经使用了上述的一些解决scheme,但它的工作原理是width
但height
。
- 如果你指定的
compileSdkVersion
大于23 ,那么可以直接使用RecyclerView提供的各自支持的回收站视图库,就像23,它会是'com.android.support:recyclerview-v7:23.2.1'
。 这些支持库支持宽度和高度的wrap_content
属性。
你必须把它添加到你的依赖
compile 'com.android.support:recyclerview-v7:23.2.1'
- 如果你的
compileSdkVersion
小于23 ,你可以使用下面提到的解决scheme。
我发现这个谷歌线程关于这个问题。 在这个线程中,有一个贡献导致LinearLayoutManager的实现。
我已经testing了它的高度和宽度,并在两种情况下都适合我。
/* * 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 */ 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. * <p/> * 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"); } } }
将measureScrapChild
replace为以下代码:
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。
在Adapter viewholder 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作为主视图,然后把它放在一个带有ScrollView的RelativeLayout中,至less是你的RecyclerView,它适用于我。
这里真正的技巧是RelativeLayout …
乐于帮助。
我没有在我的答案工作,但我知道StaggridLayoutManager没有。 网格1可以解决你的问题,因为StaggridLayout会根据内容的大小自动调整它的高度和宽度。 如果它的工作不要忘记检查它作为一个正确的答案。