我们在使用ListView的时候可以很轻松的设置列表的分割线,因为系统提供了我们外部调用接口。但是在使用RecycleView是我们会发现并没有直接设置分分割线的方面。不过我们可以自定义一个BaseItemDecoration继承 RecyclerView.ItemDecoration,通过RecycleView的addItemDecoration(ItemDecoration decor)方法进行设置。这里我们主要来看一下 BaseItemDecoration 的实现。
自定义 BaseItemDecoration 类继承自RecyclerView.ItemDecoration,重写其中的 getItemOffsets 和 onDraw 方法。
在 getItemOffsets 方法中,我们针对ItmView的四周边距进行设置,预留出分割线的位置。
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// outRect.set(left, top, right, bottom); //在Item的四周设定距离
int position = parent.getChildAdapterPosition(view);
view.setTag(position);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
int orientation = ((LinearLayoutManager) layoutManager).getOrientation();
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
int spanCount = gridLayoutManager.getSpanCount();
mMaxSpanGroupIndex = spanSizeLookup.getSpanGroupIndex(parent.getAdapter().getItemCount() - 1, spanCount);
//占用几个单元
int spanSize = spanSizeLookup.getSpanSize(position);
//在一行/列中的index
int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
//所在行/列
int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount);
if (orientation == LinearLayoutManager.VERTICAL) {
//不是第一个
if (spanSize < spanCount && spanIndex != 0) {
// 左 偏移
outRect.left = mHeightVertical;
}
//非最后行
if (spanGroupIndex < mMaxSpanGroupIndex) {
// 下 偏移
outRect.bottom = mHeightHorizontal;
}
} else {
//不是第一个
if (spanSize < spanCount && spanIndex != 0) {
// 上 偏移
outRect.top = mHeightHorizontal;
}
//非最后列
if (spanGroupIndex < mMaxSpanGroupIndex) {
// 右 偏移
outRect.right = mHeightVertical;
}
}
} else {
if (position != 0) {
if (position >= mStartPosition) {
if (orientation == LinearLayoutManager.VERTICAL) {
outRect.top = mHeightHorizontal;
} else {
outRect.left = mHeightVertical;
}
}
}
}
}
}
在 onDraw 中进行分割线的绘制,此处的绘制在ItemView的绘制之前;也可再 onDrawOver 中进行绘制,但分割线会覆盖在ItemVIew的布局只是。因此推荐使用 onDraw。
下面我们可以看下整个重写的 BaseItemDecoration
public class YTBaseItemDecoration extends RecyclerView.ItemDecoration {
/**
* 分割线尺寸
* 注意:这里的水平竖向是针对屏幕而言
* 水平分割线高度竖向分割线高度
*/
private int mHeightHorizontal, mHeightVertical;
/**
* 最大多少行/列
*/
private int mMaxSpanGroupIndex;
private Paint mPaint;
/**
* 从特定位置开始绘制,只对 LinearLayoutManager 横行/竖向分割线有效,网格grid无效
*/
private int mStartPosition = 0;
/**
* margin配置,只对 LinearLayoutManager 横行/竖向生效,网格grid无效
*/
private int mMarginLeft, mMarginRight;
/**
* 构造1
*
* @param height 分割线尺寸
*/
public YTBaseItemDecoration(int height) {
mHeightHorizontal = height;
mHeightVertical = height;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.TRANSPARENT);
mPaint.setStyle(Paint.Style.FILL);
}
/**
* 构造2
*
* @param height 分割线尺寸
* @param color 分割线填充色
*/
public YTBaseItemDecoration(int height, @ColorInt int color) {
mHeightHorizontal = height;
mHeightVertical = height;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(color);
mPaint.setStyle(Paint.Style.FILL);
}
/**
* 构造3
*
* @param height 分割线尺寸
* @param color 分割线填充色
* @param margin margin
*/
public YTBaseItemDecoration(int height, @ColorInt int color, int margin) {
mHeightHorizontal = height;
mHeightVertical = height;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(color);
mPaint.setStyle(Paint.Style.FILL);
mMarginLeft = margin;
mMarginRight = margin;
}
/**
* 设置填充色
*
* @param color 分割线色值
* @return YTBaseItemDecoration
*/
public YTBaseItemDecoration setColor(@ColorInt int color) {
mPaint.setColor(color);
return this;
}
/**
* 设置分割线尺寸
*
* @param height 尺寸
* @return YTBaseItemDecoration
*/
public YTBaseItemDecoration setHeight(int height) {
mHeightHorizontal = height;
mHeightVertical = height;
return this;
}
/**
* 设置分割线从固定position开始绘制
*
* @param start
* @return
*/
public YTBaseItemDecoration setStartPosition(int start) {
mStartPosition = start;
return this;
}
/**
* margin
*
* @param margin
* @return
*/
public YTBaseItemDecoration setMargin(int margin) {
mMarginLeft = margin;
mMarginRight = margin;
return this;
}
/**
* margin
*
* @param marginLeft
* @param marginRight
* @return
*/
public YTBaseItemDecoration setMargin(int marginLeft, int marginRight) {
mMarginLeft = marginLeft;
mMarginRight = marginRight;
return this;
}
/**
* 设置分割线尺寸,主要用于grid横竖方向分割线粗细不一致
*
* @param heightHorizontal 水平方向尺寸
* @param heightVertical 竖直方向尺寸
* @return
*/
public YTBaseItemDecoration setHeight(int heightHorizontal, int heightVertical) {
mHeightHorizontal = heightHorizontal;
mHeightVertical = heightVertical;
return this;
}
@Override
public void onDraw(@NotNull Canvas canvas, @NotNull RecyclerView parent, @NotNull RecyclerView.State state) {
super.onDrawOver(canvas, parent, state);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
drawSpace(canvas, parent);
} else if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if (linearLayoutManager.getOrientation() == RecyclerView.VERTICAL) {
drawVertical(canvas, parent);
} else {
drawHorizontal(canvas, parent);
}
}
}
private final Rect mBounds = new Rect();
/**
* LinearLayoutManager.VERTICAL 绘制水平分割线
*
* @param canvas
* @param parent
*/
private void drawVertical(Canvas canvas, RecyclerView parent) {
int left, right, top, bottom;
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(left, parent.getPaddingTop(), right,
parent.getHeight() - parent.getPaddingBottom());
}
int childCount = parent.getChildCount();
for (int i = 1; i < childCount; ++i) {
if (i > mStartPosition) {
View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, this.mBounds);
left = mBounds.left + mMarginLeft;
right = mBounds.right - mMarginRight;
top = mBounds.top;
bottom = top + mHeightHorizontal;
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
/**
* LinearLayoutManager.HORIZONTAL 绘制竖直分割线
*
* @param canvas
* @param parent
*/
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
int left, right, top, bottom;
if (parent.getClipToPadding()) {
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
canvas.clipRect(parent.getPaddingLeft(), top,
parent.getWidth() - parent.getPaddingRight(), bottom);
}
int childCount = parent.getChildCount();
for (int i = 1; i < childCount; ++i) {
if (i > mStartPosition) {
View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, mBounds);
left = mBounds.left;
right = left + mHeightVertical;
top = mBounds.top + mMarginLeft;
bottom = mBounds.bottom - mMarginRight;
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
/**
* GridLayoutManager 分割线
*
* @param canvas
* @param parent
*/
private void drawSpace(Canvas canvas, RecyclerView parent) {
int top, bottom, left, right;
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
top = parent.getPaddingTop();
right = parent.getWidth() - parent.getPaddingRight();
bottom = parent.getHeight() - parent.getPaddingBottom();
canvas.clipRect(left, top, right, bottom);
}
GridLayoutManager gridLayoutManager = (GridLayoutManager) parent.getLayoutManager();
int spanCount = gridLayoutManager.getSpanCount();
GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
// 绘制思路,以绘制bottom和left为主,top和right不绘制,需要判断出当前的item是否位于边缘,位于边缘的item不绘制bottom和left,你懂得
View child = parent.getChildAt(i);
int position = (int) child.getTag();
parent.getDecoratedBoundsWithMargins(child, this.mBounds);
//所在行/列
int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount);
//item占用单元数
int spanSize = spanSizeLookup.getSpanSize(position);
//在行/列中的index
int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
if (gridLayoutManager.getOrientation() == RecyclerView.VERTICAL) {
//预留空间为左+下
// 非每行第1个,绘制 left 竖向分割
if (spanIndex != 0 && spanSize < spanCount) {
left = mBounds.left;
right = left + mHeightVertical;
top = mBounds.top + mMarginLeft;
if (spanGroupIndex == mMaxSpanGroupIndex) {
//最后一行未预留bottom空间,不考虑重叠
bottom = mBounds.bottom - mMarginRight;
} else if (mMarginRight > 0) {
//存在margin时需要考虑重叠影响,减去margin及水平分割线尺寸,
bottom = mBounds.bottom - mMarginRight - mHeightHorizontal;
} else {
bottom = mBounds.bottom;
}
canvas.drawRect(left, top, right, bottom, mPaint);
}
//非最后行,绘制 bottom 横向分割
if (spanGroupIndex < mMaxSpanGroupIndex) {
if (spanIndex == 0) {
//每行第一个左侧无预留空间,不考虑重叠问题
left = mBounds.left + mMarginLeft;
} else if (mMarginLeft > 0) {
//处理margin与水平线重叠
left = mBounds.left + mMarginLeft + mHeightVertical;
} else {
left = mBounds.left;
}
right = mBounds.right - mMarginRight;
bottom = mBounds.bottom;
top = bottom - mHeightHorizontal;
canvas.drawRect(left, top, right, bottom, mPaint);
}
//最后一个若未撑满最后一行,则需要绘制右侧分割线,
// 最后一项未预留空间,无需考虑margin与分割线重叠问题
if (position == parent.getAdapter().getItemCount() - 1) {
//计算最后一行所有item是否占满空间,若未满则增加右偏移绘制分割线
int span = spanCount;
for (int pos = position; pos <= position; pos--) {
span = span - spanSizeLookup.getSpanSize(pos);
int index = spanSizeLookup.getSpanIndex(pos, spanCount);
if (index == 0) {
break;
}
}
//最后一行/列未满
if (span > 0) {
// 右 偏移
left = mBounds.right;
right = mBounds.right + mHeightVertical;
top = mBounds.top + mMarginLeft;
bottom = mBounds.bottom - mMarginRight;
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
} else {
//预留空间为上+右
// 非每列第1个,绘制 top 分割
if (spanSize < spanCount && spanIndex != 0) {
left = mBounds.left + mMarginLeft;
if (spanGroupIndex == mMaxSpanGroupIndex) {
//最后一列未预留right空间,不考虑重叠
right = mBounds.right - mMarginRight;
} else if (mMarginRight > 0) {
//存在margin时需要考虑重叠影响,减去margin及竖向分割线尺寸,
right = mBounds.right - mMarginRight - mHeightVertical;
} else {
right = mBounds.right;
}
top = mBounds.top;
bottom = top + mHeightHorizontal;
canvas.drawRect(left, top, right, bottom, mPaint);
}
//非最后列,绘制 right 分割
if (spanGroupIndex < mMaxSpanGroupIndex) {
left = mBounds.right - mHeightVertical;
right = mBounds.right;
if (spanIndex == 0) {
//每列第一个右侧无预留空间,不考虑重叠问题
top = mBounds.top + mMarginLeft;
} else if (mMarginLeft > 0) {
//处理margin与竖向线重叠
top = mBounds.top + mMarginLeft + mHeightHorizontal;
} else {
top = mBounds.top;
}
bottom = mBounds.bottom - mMarginRight;
canvas.drawRect(left, top, right, bottom, mPaint);
}
//最后一个若未撑满最后一行,则需要绘制下侧分割线
// 最后一项未预留空间,无需考虑margin与分割线重叠问题
if (position == parent.getAdapter().getItemCount() - 1) {
//计算最后一行所有item是否占满空间,若未满则增加右偏移绘制分割线
int span = spanCount;
for (int pos = position; pos <= position; pos--) {
span = span - spanSizeLookup.getSpanSize(pos);
int index = spanSizeLookup.getSpanIndex(pos, spanCount);
if (index == 0) {
break;
}
}
if (span > 0) {
left = mBounds.left + mMarginLeft;
right = mBounds.right - mMarginRight;
top = mBounds.bottom;
bottom = top + mHeightHorizontal;
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
}
}
此处针对该自定义类再做说明,其中的mIsShowTopDivider, mIsShowBottomDivider, mIsShowLeftDivider, mIsShowRightDivider 均对应直观意义上的上下左右,RecyclerView 纵滑 则底部为bottom,横滑则底部问right ,请自行脑补。