Android RecyclerView —— 基本使用
Android RecyclerView —— 适配器封装探索
前面说了 RecyclerView
的基本使用以及对适配器的封装,但是在使用 ListView
时,有 dividerHeight
和 divider
属性用来设置分割线的高度和颜色(Drawable
),但是在 RecyclerView
中并没有这个属性,而且也没有替代的属性,也就是说,在 RecyclerView
并我们不能直接设置分割线,而需要使用 RecyclerView
提供的方法 addItemDecoration(@NonNull RecyclerView.ItemDecoration decor)
来增加分割线。 RecyclerView.ItemDecoration
需要由我们自己实现。主要需要实现的有三个方法(真正实现的其实只有2个):
// 在 item 绘制之前调用(就是绘制在 item 的底层) [和 onDrawOver() 方法二选一即可]
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDraw(c, parent);
}
// 在 item 绘制之后调用(就是绘制在 item 的上层) [和 onDraw() 方法二选一即可]
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDrawOver(c, parent);
}
// 设置偏移量
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.getItemOffsets(outRect, ((RecyclerView.LayoutParams)view.getLayoutParams()).getViewLayoutPosition(), parent);
}
getItemOffsets()
实际上是在条目之间分配一块矩形区域用来放置我们的分割线,注意一下方法参数Rect outRect,而通过 onDraw()
方法或者 onDrawOver()
方法把颜色(Drawable)画在刚刚申请的间隔矩形中;这样就相当于增加了分割线。
提示:在 support 包的版本是 25或以上时,系统提供了一个默认绘制分割线的实现 DividerItemDecoration
,但是该实现只针对 LinearLayoutManager
。而对于 GridLayoutManager
并没有默认的实现,所以我们需要自定义实现。
LinearLayoutManager
类型。public class LinearDividerDecoration extends RecyclerView.ItemDecoration {
private int mDividerHeight = 40;
private int mDividerColor = 0xFFFF0000;
private Paint mPaint;
private int mOrientation;
public LinearDividerDecoration(@RecyclerView.Orientation int orientation) {
this.mOrientation = orientation;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(mDividerColor);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {
outRect.set(0, 0, 0, mDividerHeight);
} else {
outRect.set(0, 0, mDividerHeight, 0);
}
}
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
canvas.save();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; ++i) {
View childAt = parent.getChildAt(i);
int left = 0;
int right = parent.getWidth();
int top = childAt.getBottom() ;
int bottom = childAt.getBottom()+ mDividerHeight;
canvas.drawRect(left, top, right, bottom, mPaint);
}
canvas.restore();
}
}
使用:
// 增加分割线
recyclerView.addItemDecoration(new LinearDividerDecoration(RecyclerView.VERTICAL));
GridLayoutManager
的分割线类。public class GridDividerDecoration extends RecyclerView.ItemDecoration {
private int mDividerHeight = 4;
private int mDividerColor = 0xFFFF0000;
private Paint mPaint;
private int mOrientation;
public GridDividerDecoration(@RecyclerView.Orientation int orientation) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(mDividerColor);
mOrientation = orientation;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
int itemCount = parent.getAdapter().getItemCount();
int viewLayoutPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
int left = 0;
int top = 0;
int right = mDividerHeight;
int bottom = mDividerHeight;
if (isLastRow(layoutManager, itemCount, viewLayoutPosition)) {
// 如果是最后一行,则不需要绘制底部
bottom = 0;
}
if (isLastCol(layoutManager, itemCount, viewLayoutPosition)) {
// 如果是最后一列,则不需要绘制右边
right = 0;
}
outRect.set(left, top, right, bottom);
}
/**
* 判断是否最后一列
*/
private boolean isLastCol(GridLayoutManager layoutManager, int childCount, int itemPosition) {
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
int spanIndex = spanSizeLookup.getSpanIndex(itemPosition, spanCount);
int spanSize = spanSizeLookup.getSpanSize(itemPosition);
if (mOrientation == GridLayoutManager.VERTICAL) {
return spanIndex + spanSize == spanCount;
} else {
return (childCount - itemPosition) / (spanCount * 1.0f) <= 1;
}
}
/**
* 判断是否最后一行
*/
private boolean isLastRow(GridLayoutManager layoutManager, int childCount, int itemPosition) {
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
int spanIndex = spanSizeLookup.getSpanIndex(itemPosition, spanCount);
int spanSize = spanSizeLookup.getSpanSize(itemPosition);
if (mOrientation == GridLayoutManager.VERTICAL) {
return (childCount - itemPosition) / (spanCount * 1.0f) <= 1;
} else {
return spanIndex + spanSize == spanCount;
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
c.save();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View childAt = parent.getChildAt(i);
drawHorizontal(c, childAt);
drawVertical(c, childAt);
}
c.restore();
}
public void drawHorizontal(Canvas c, View childAt) {
int left = childAt.getLeft();
int right = childAt.getRight();
int top = childAt.getBottom() ;
int bottom = childAt.getBottom()+ mDividerHeight;
c.drawRect(left, top, right, bottom, mPaint);
}
public void drawVertical(Canvas c, View childAt) {
int left = childAt.getRight();
int right = left + mDividerHeight;
int top = childAt.getTop();
int bottom = childAt.getBottom() + mDividerHeight;
c.drawRect(left, top, right, bottom, mPaint);
}
}
使用:
// 增加分割线
recyclerView.addItemDecoration(new GridDividerDecoration(GridLayoutManager.VERTICAL));
看上去好像没什么问题,但是我们把 mDividerHeight
调大到 50,效果图如下:
上图我们能明显的发现item的宽度不一样了,这是什么原因造成的了,这是因为分割线占用的控件是属于 item 的空间,那么当 GridLayoutManager
设置了 spanCount
时,RecyclerView
就会把整个控件的宽度平均分配成 spanCount 份,每一个item的大小都是一样的,但是因为我们在绘制列分割线时,最后一样没有绘制分割线,所以导致会后一列的item宽度明显大于前面的宽度,从而造成了item宽度不一样的问题。
GridLayoutManager
添加了分割线之后 item 宽度不同的问题说明:我们把整个item的宽度定义为width,然后左右的分割线宽度为 lw(需要的话),中间的分割线宽度为 dw,那么一共的分割线宽度就是:total = (spanCount - 1) * dw + 2 * lw;这样一来就能计算出每个item需要留出的空间:item = total / spanCount;因为每个item留出的相等的空间,那么item剩余的空间也是相同的了,也就是item的宽度也就是一样的了。
具体的代码实现,请看这里。对 RecyclerView
的分割线进行了封装,实现了自定义设置分割线宽、高,是都需要绘制四周的分割线,以及四周分割线的宽高,还有分割线的颜色、交叉点的颜色等。
去github下载相关代码及实例