最近有这样的一个需求,就是条目铺满整个屏幕,1-3-5的时候单列铺满整个屏幕,6+ 以上的时候两列,
采用的是一下 方法 0:
//RecyclerView大小固定的情况下,根据RecyclerView的宽高设置ItemView的宽高,以达到recyclerview
//刚好显示N行/列数据的目的:
//在原理是先计算出RecyclerView的宽高,然后在Adapter的onCreateViewHolder中设置view的高度:
//比如一个垂直列表,希望recyclerview刚好显示三行,这样写:
@Override
public TestAdapter.TestItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
view.getLayoutParams().height = mRecyclerViewHeight/3;
return new TestItemViewHolder (view);
这种比较简单,还有一些其他方法,可以一起记录一下:
关于RecyclerView的宽高调整
- 设置ItemView的间隔高宽
重写ItemDecoration的getItemOffsets函数即可:
recycleview.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(4, 4, 4, 4);//设置itemView中内容相对边框左,上,右,下距离
}
});
- itemView 适应recyclerview
(见方法 0)
- recyclerview适应ItemView
ItemView大小固定的情况下,根据ItemView的宽高调整recyclerview的宽高,以达到recyclerview刚好显示N行/列数据的目的:
原理是重写LayoutManager的onMeasure方法,计算itemview的宽高,进而计算出recyclerview的宽高。
比如,一个垂直列表,希望recyclerview刚好显示三行,这样写:
recyclerView.setLayoutManager(new LinearLayoutManager(this) {
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
if (getChildCount() > 0) {
View firstChildView = recycler.getViewForPosition(0);
measureChild(firstChildView, widthSpec, heightSpec);
setMeasuredDimension(View.MeasureSpec.getSize(widthSpec), firstChildView.getMeasuredHeight()*3);
} else {
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
});
这种不符合我们的需求,但也是一种思路.
自定义LayoutManager
首先,需要自定义一个 LinearLayoutManager,这里 RecyclerView 在 onMeasure 回调中会调用 LinearLayoutManager 的 onMeasure 方法,所以需要在 LinearLayoutManager 的 onMeasure 中做一些高度设置的处理,大致内容:
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,int heightSpec) {
View view = recycler.getViewForPosition(0);
measureChild(view, widthSpec, heightSpec);
int measuredWidth = View.MeasureSpec.getSize(widthSpec);
int measuredHeight = view.getMeasuredHeight();
setMeasuredDimension(measuredWidth, measuredHeight);
}
两个注意点:这里获取了 view 的高度,也就是 item 布局的高度,所以 item 的布局需要设定固定的高度,否则获取为 0。其次,
mLayoutManager.setAutoMeasureEnabled(false)
mList.setHasFixedSize(false)
这两个设置不能少,否则报错,这里和 RecyclerView.onMeasure 中的调用顺序有关,源码:
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.mAutoMeasure) { //这里为true会进入该分支
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); //在调用mLayout的onMeasure方法时(被自定义复写的方法),mState.mItemCount为0,造成越界异常
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
if (mHasFixedSize) { //这里不设置为false会造成与上面相同的问题
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
processAdapterUpdatesAndSetAnimationFlags();
if (mState.mRunPredictiveAnimations) {
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;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); //在这里调用时mState.mItemCount才会有值,这与mLayout中获取当前item布局的方式有关:View view = recycler.getViewForPosition(0);
resumeRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
RecyclerView 用法之Span
使用布局管理器来管理布局。我们继续观察首页布局的图示,我们真的要为了实现这种混合布局自己去写一个布局管理器吗?我们发现上面出现了列表、网格、瀑布流3种交叉混排的混合布局。我们先把瀑布流放在一边,仔细想想如果我们把网格的列数设置为1列,那不就是一个列表布局吗,也就是说我们使用网格布局管理器就可以做出列表的样式,所以说虽然是说用自定义布局管理器,但实际上不需要我们自定义,GridLayoutManager为我们提供了动态改变每个item所占列数的方法:
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return gridManager.getSpanCount();
}
}
getSpanSize方法,返回值就表示当前item占多少列,例如如果我们列数设置的为3列,返回3就表示铺满,也就是和列表一样了。
如图所示,我们给RecyclerView设置一个列数为6的GridLayoutManager,然后再动态地为不同部位的item分别设置SpanSize为6(铺满)、3(1/2)、2(1/3)就行了
设置一个列数为6的GridLayoutManager:
recyclerView.setLayoutManager(new GridLayoutManager(recyclerView.getContext(), 6, GridLayoutManager.VERTICAL, false));
在onAttachedToRecyclerView方法中动态为不同position设置不同的SpanSize:
@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int type = getItemViewType(position);
switch (type){
case TYPE_SLIDER:
case TYPE_TYPE2_HEAD:
case TYPE_TYPE3_HEAD:
return 6;
case TYPE_TYPE2:
return 3;
case TYPE_TYPE3:
return 2;
default:
return 3;
}
}
});
}
}
我查阅了StaggeredGridLayoutManager,发现它并没有提供动态设置所占列的方法,只是在StaggeredGridLayoutManager.LayoutParams中提供了这样一个方法:
LayoutParams.setFullSpan(true);
当然RecyclerView 用法之Span并不能直接解决我们的问题,但是这也是一种思路,很不错的思路,可以试一下;