最近,在使用RecyclerView时遇到一个问题,就是将RecyclerView的高度设置为“wrap_content”时,控件实际测量高度为”match_parent”,为什么会出现这种问题呢?RecyclerView设置如下:
.support.v7.widget.RecyclerView
android:id="@+id/sv_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray">
.support.v7.widget.RecyclerView>
recyclerView = (RecyclerView)findViewById(R.id.sv_rv);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
recyclerView.setAdapter(new RvAdapter());
一般来说,出现这种问题是因为高度测量问题,所以先查看RecyclerView的onMeasure方法,如下:
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
processAdapterUpdatesAndSetAnimationFlags();
if (mState.mRunPredictiveAnimations) {
// TODO: try to provide a better approach.
// When RV decides to run predictive animations, we need to measure in pre-layout
// state so that pre-layout pass results in correct layout.
// On the other hand, this will prevent the layout manager from resizing properly.
mState.mInPreLayout = true;
} else {
// consume remaining updates to provide a consistent state with the layout pass.
mAdapterHelper.consumeUpdatesInOnePass();
mState.mInPreLayout = false;
}
mAdapterUpdateDuringMeasure = false;
resumeRequestLayout(false);
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
} else {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
}
mState.mInPreLayout = false; // clear
}
重要代码:
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
} else {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
}
}
可以发现当RecyclerView设置LayoutManager时,RecyclerView的测量高度由所选择的LayoutManager决定。由于上文所选择的LayoutManager为LinearLayoutManager,故查看其源码的onMeasure函数,然而并没找到,那就查看其父类的onMeasure函数。
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
然后查看defaultOnMeasure函数,可以发现其并未对子item进行高度测量,所以导致其实测高度异常。故而要纠正此问题,需要重写LayoutManager的onMeasure函数。
如下,是我自己写的一个简单的LayoutManager子类,继承自LinearLayoutManager,仅作抛砖引玉之用:
package how.th.ridelib.RvLayoutManager;
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class RecyclerLayoutManager extends LinearLayoutManager {
public RecyclerLayoutManager(Context context){
super(context);
}
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
int itemCount = state.getItemCount();
int measuredWidth = 0;
int measuredHeight = 0;
int heightSize = View.MeasureSpec.getSize(heightSpec);
for(int i = 0; i < itemCount; i ++){
View view = recycler.getViewForPosition(i);
if(view != null){
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams)view.getLayoutParams();
int margin = layoutParams.bottomMargin + layoutParams.topMargin;
measuredWidth = View.MeasureSpec.getSize(widthSpec);
view.measure(widthSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
switch (layoutParams.height){
case RecyclerView.LayoutParams.WRAP_CONTENT:
measuredHeight += (getDecoratedMeasuredHeight(view) + margin);
break;
case RecyclerView.LayoutParams.MATCH_PARENT:
default:
measuredHeight += (layoutParams.height + margin);
break;
}
}
}
if(measuredHeight > heightSize)
super.onMeasure(recycler, state, widthSpec, heightSpec);
else
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
实际使用:
recyclerView = (RecyclerView)findViewById(R.id.sv_rv);
recyclerView.setLayoutManager(new RecyclerLayoutManager(getApplicationContext()));
recyclerView.setAdapter(new RvAdapter());