当RecyclerView属性设置为wrap_content
+maxHeight
时,maxHeight没有效果。
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
...
android:layout_height="wrap_content"
android:maxHeight="300dp"
...
/>
查看源码时发现,当RecyclerView的LayoutManager#isAutoMeasureEnabled()返回true时,RecyclerView高度取决于children view的布局高度,并非取决于RecyclerView自身的测量高度。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
...
if (mLayout.mAutoMeasure) {
...
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
if (mLayout.shouldMeasureTwice()) {
...
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
...
}
}
下面是setMeasuredDimensionFromChildren(int widthSpec, int heightSpec)
源码
void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
final int count = getChildCount();
if (count == 0) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
return;
}
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
final Rect bounds = mRecyclerView.mTempRect;
getDecoratedBoundsWithMargins(child, bounds);
if (bounds.left < minX) {
minX = bounds.left;
}
if (bounds.right > maxX) {
maxX = bounds.right;
}
if (bounds.top < minY) {
minY = bounds.top;
}
if (bounds.bottom > maxY) {
maxY = bounds.bottom;
}
}
mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);
}
方法计算了RecyclerView当前所有Child View的布局范围mRecyclerView.mTempRect
,最后调用了public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec)
,并将得出的布局范围mRecyclerView.mTempRect
、RecyclerView的测量参数widthSpec
、heightSpec
作为参数传入,以此来决定RecyclerView最终宽高值。
public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
int width = chooseSize(wSpec, usedWidth, getMinimumWidth());
int height = chooseSize(hSpec, usedHeight, getMinimumHeight());
setMeasuredDimension(width, height);
}
在这个方法中,RecyclerView的宽高通过chooseSize()
方法最终决定。
public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
return size;
case View.MeasureSpec.AT_MOST:
return Math.min(size, Math.max(desired, min));
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
可以看到,当测量模式mode
为EXACTLY(明确指定宽高)时,RecyclerView大小即为指定的大小size
;当测量模式为UNSPECIFIED
时,RecyclerView大小取Child View布局范围和RecyclerView宽高最小值中大的值;当测量模式为AT_MOST
时,取测量高度size
和Math.max(desired, min)
(Child View布局范围和RecyclerView宽高最小值中大的值)的小值。
解决办法
因此,我们只需要重写LayoutManager的public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec)
方法即可为RecyclerView设置最大宽高。
override fun setMeasuredDimension(childrenBounds: Rect, wSpec: Int, hSpec: Int) {
super.setMeasuredDimension(childrenBounds, wSpec, View.MeasureSpec.makeMeasureSpec(maxHeight, AT_MOST))
}