话不多说,先上视频
1588730208180540.mp4
上代码
package com.example.decoration;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Context mContext;
private Paint mPaint;//绘制分割线样式
private int mSpacingWidth = 0;//分割线宽度,默认为0px(仅左右边距)
private int mSpacingHeight = 0;//分割线高度,默认为0px
private int mSpacingMarginLeft = 0;//不显示最后一行Item的底部间距
private int mSpacingMarginRight = 0;//不显示最后一行Item的底部间距
private int mOrientation;// 0:多行单列
private boolean mShowTop = false;//不显示第一行Item的头部间距
private boolean mShowRight = false;//不显示最后一行Item的底部间距
private boolean mShowBottom = false;//不显示最后一行Item的底部间距
private boolean mShowLeft = false;//不显示最后一行Item的底部间距
private boolean mIsMarginSame = false;//上下边距是否与左右边距相同
private boolean mIsWidthSame = false;//多行多列,列宽是否与左右边距相同
private boolean mIsHeader = false;//是否有Header
private boolean mIsFooter = false;//是否有Footer
private int mMarginTop = 0, mMarginRight = 0, mMarginBottom = 0, mMarginLeft = 0;
public static final int MULTI_LINE_SINGLE_ROW = 0;//多行单列
public static final int MULTI_LINE_SINGLE_ROW_HF = 1;//多行单列_带Header
public static final int MULTI_LINE_MULTI_ROW = 2;//多行多列
public static final int SINGLE_LINE_MULTI_ROW = 3;//单行多列
public DividerItemDecoration(Context context, int spacingHeight, int orientation){
mContext = context;
mSpacingHeight = dp2px(spacingHeight);
mSpacingWidth = mSpacingHeight;
mOrientation = orientation;
}
public DividerItemDecoration(Context context, int spacingWidth, int spacingHeight, int orientation){
this(context, spacingHeight, orientation);
mSpacingWidth = dp2px(spacingWidth);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int itemCount = parent.getAdapter() != null ? parent.getAdapter().getItemCount() : 0;//item总数
int index = parent.getChildLayoutPosition(view); //当前下标
int spanCount = getSpanCount(parent);//多行多列,总列数
switch (mOrientation){
case MULTI_LINE_SINGLE_ROW://多行单列
outRect.set(mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0,
index == 0 && mShowTop ? mMarginTop > 0 ? mMarginTop : mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0,
mShowRight ? mMarginRight > 0 ? mMarginRight : mSpacingWidth : 0,
index == itemCount - 1 ? mShowBottom ? mMarginBottom > 0 ? mMarginBottom :
mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0 : mSpacingHeight);
break;
case MULTI_LINE_SINGLE_ROW_HF://多行单列_带Header(支持带Footer,需设置mIsFooter=true)
if (index == 0 || (mIsFooter && index == itemCount - 1))
outRect.set(0, 0, 0, 0);
else
outRect.set(mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0,
index == 1 && mShowTop ? mMarginTop > 0 ? mMarginTop : mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0,
mShowRight ? mMarginRight > 0 ? mMarginRight : mSpacingWidth : 0,
index == itemCount - (mIsFooter ? 2 : 1) ? mShowBottom ? mMarginBottom > 0 ? mMarginBottom :
mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0 : mSpacingHeight);
break;
case MULTI_LINE_MULTI_ROW://多行多列(支持带Header,需设置mIsHeader=true;支持带Footer,需设置mIsFooter=true)
if ((mIsHeader && index == 0) || (mIsFooter && index == itemCount - 1)){
outRect.set(0, 0, 0, 0);
break;
}
index -= mIsHeader ? 1 : 0;
itemCount -= mIsFooter ? 2 : mIsHeader ? 1 : 0;
int spanIndex = getSpanIndex(parent, view);//当前列
int lastRowNum = itemCount % spanCount;//最后一行余数
int widthSame = mIsWidthSame ? mSpacingWidth : mSpacingHeight;//列间距
boolean isStartColumn = spanIndex == 0;//第一列
boolean isLastColumn = spanIndex == spanCount - 1;//最后一列
boolean isStartRow = index < spanCount;//第一行
boolean isLastRow = itemCount - index <= (lastRowNum == 0 ? spanCount : lastRowNum);//最后一行
//左间距
double marginLeft = mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0;
//有间距
double marginRight = mShowRight ? mMarginRight > 0 ? mMarginRight : mSpacingWidth : 0;
//Item右边留白=总列间距/总列数
double spanBlank = ((widthSame * (spanCount - 1.0)) + marginLeft + marginRight) / spanCount;
//左偏移量=(列间距-Item右边留白)*当前列
double offset = (widthSame - spanBlank) * spanIndex;
//有左右边距时在原偏移量上多增加的偏移量
double offsetMore = offset + widthSame + (mMarginLeft > 0 ? mMarginLeft : mSpacingWidth) - widthSame;
int left = isStartColumn ? mShowLeft ? mMarginLeft > 0 ? mMarginLeft :
mSpacingWidth : 0 : mShowLeft ? (int) offsetMore : (int) offset;
int top = isStartRow && mShowTop ? mMarginTop > 0 ? mMarginTop : mIsMarginSame ?
mSpacingWidth : mSpacingHeight : 0;
int bottom = isLastRow ? mShowBottom ? mMarginBottom > 0 ? mMarginBottom : mIsMarginSame ?
mSpacingWidth : mSpacingHeight : 0 : mSpacingHeight;
outRect.set(left, top, -left, bottom);
break;
case SINGLE_LINE_MULTI_ROW://单行多列 (不支持Header、Footer)
outRect.set(
index == 0 ? mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth :
0 : mIsWidthSame ? mSpacingWidth : mSpacingHeight,
mShowTop ? mMarginTop > 0 ? mMarginTop : mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0,
index == itemCount - 1 && mShowRight ? mMarginRight > 0 ? mMarginRight : mSpacingWidth : 0,
mShowBottom ? mMarginBottom > 0 ? mMarginBottom : mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0
);
break;
}
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mPaint == null) return;
float left = 0, top = 0, right = 0, bottom = 0;
float topL = 0, leftL = + parent.getPaddingLeft(), bottomL = 0, leftR = 0, rightL = 0;
int childCount = parent.getChildCount();//可见Item数量
int itemCount = parent.getAdapter() != null ? parent.getAdapter().getItemCount() : 0;//item总数
switch (mOrientation){
case MULTI_LINE_SINGLE_ROW://多行单列
for (int i = 0; i < childCount; i++){
View itemView = parent.getChildAt(i);
int index = parent.getChildLayoutPosition(itemView);//当前下标
left = itemView.getLeft() + mSpacingMarginLeft;
//矩形Y轴开始位置=Item高+上一个Y轴结束位置+第一条可见Item距顶部的距离
top = itemView.getHeight() + bottom + (i == 0 ? itemView.getTop() : 0);
right = left + itemView.getWidth() - (mSpacingMarginLeft + mSpacingMarginRight);
bottom = top + (index == itemCount - 1 ? 0 : mSpacingHeight);
c.drawRect(left, top, right, bottom, mPaint);
//绘制顶部分割线颜色
if (index == 0 && mShowTop)
c.drawRect(left, 0, right, itemView.getTop(), mPaint);
//绘制底部分割线颜色
if (index == itemCount - 1 && mShowBottom)
c.drawRect(left, top, right, top + (mMarginBottom > 0 ?
mMarginBottom : mIsMarginSame ? mSpacingWidth : mSpacingHeight), mPaint);
topL = bottomL + (i == 0 ? itemView.getTop() : 0)
- (index == 0 && mShowTop ? mMarginTop > 0 ? mMarginTop :
mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0);
bottomL = topL + itemView.getHeight() + (index == itemCount - 1 ?
mShowBottom ? mMarginBottom > 0 ? mMarginBottom : mIsMarginSame ?
mSpacingWidth : mSpacingHeight : 0 : mSpacingHeight)
+ (index == 0 && mShowTop ? mMarginTop > 0 ? mMarginTop :
mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0);
leftR = leftL + itemView.getWidth() + (mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0);
//绘制左间距颜色
if (mShowLeft)
c.drawRect(leftL, topL, leftL + (mMarginLeft > 0 ? mMarginLeft : mSpacingWidth), bottomL, mPaint);
//绘制右间距颜色
if (mShowRight)
c.drawRect(leftR, topL, leftR + (mMarginRight > 0 ? mMarginRight : mSpacingWidth), bottomL, mPaint);
}
break;
case MULTI_LINE_SINGLE_ROW_HF://多行单列_带Header
for (int i = 0; i < childCount; i++){
View itemView = parent.getChildAt(i);
int index = parent.getChildLayoutPosition(itemView);
//有Footer,最后一个Item不绘制
if (index == itemCount - 1 && mIsFooter) continue;
//绘制顶部分割线颜色
if (index == 0){
if (!mShowTop){
bottom = itemView.getHeight() + itemView.getTop();
topL = bottom;
continue;
}
left = itemView.getLeft() + (mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0);
top = itemView.getHeight() + itemView.getTop();
right = itemView.getWidth() - (mShowRight ? mMarginRight > 0 ? mMarginRight : mSpacingWidth : 0);
bottom = top + (mMarginTop > 0 ? mMarginTop : mIsMarginSame ? mSpacingWidth : mSpacingHeight);
c.drawRect(left, top, right, bottom, mPaint);
topL = top;
continue;
}
//计算左右边距开始位置
topL = (index == 1 ? topL : 0) + bottomL + (i == 0 ? itemView.getTop()
- (index == 1 ? mShowTop ? mMarginTop > 0 ? mMarginTop :
mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0 : 0) : 0);
//计算左右边距结束位置
bottomL = topL + itemView.getHeight()
+ (index == 1 && mShowTop ? mMarginTop > 0 ? mMarginTop :
mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0)
+ (index == itemCount - (mIsFooter ? 2 : 1) && mShowBottom ? mMarginBottom > 0 ?
mMarginBottom : mIsMarginSame ? mSpacingWidth : mSpacingHeight : mSpacingHeight)
- (mIsFooter && !mShowBottom && index == itemCount - (mIsFooter ? 2 : 1) ? mSpacingHeight : 0);
leftR = leftL + itemView.getWidth() + (mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0);
//绘制左间距颜色
if (mShowLeft) c.drawRect(leftL, topL, leftL + (
mMarginLeft > 0 ? mMarginLeft : mSpacingWidth), bottomL, mPaint);
//绘制右间距颜色
if (mShowRight) c.drawRect(leftR, topL, leftR + (
mMarginRight > 0 ? mMarginRight : mSpacingWidth), bottomL, mPaint);
left = itemView.getLeft();
//矩形Y轴开始位置=Item高+上一个Y轴结束位置+第一条可见Item距顶部的距离
top = itemView.getHeight() + bottom + (i == 0 ? itemView.getTop() : 0);
right = left + itemView.getWidth();
bottom = top + (index == itemCount - (mIsFooter ? 2 : 1) ? 0 : mSpacingHeight);
c.drawRect(left, top, right, bottom, mPaint);
//绘制顶部分割线颜色(当Header不在视图中可见时)
if (i == 0 && index == 1 && itemView.getTop() >= 0 && mShowTop)
c.drawRect(left, 0, right, itemView.getTop(), mPaint);
//绘制底部分割线颜色
if (index == itemCount - (mIsFooter ? 2 : 1) && mShowBottom)
c.drawRect(left, top, right, top + (mMarginBottom > 0 ?
mMarginBottom : mIsMarginSame ? mSpacingWidth : mSpacingHeight), mPaint);
}
break;
case MULTI_LINE_MULTI_ROW://多行多列(支持带Header,需设置mIsHeader=true;支持带Footer,需设置mIsFooter=true)
int spanCount = getSpanCount(parent);//多行多列,总列数
for (int i = 0; i < childCount; i++) {
View itemView = parent.getChildAt(i);
int index = parent.getChildLayoutPosition(itemView);//当前下标
if ((mIsHeader && index == 0) || (mIsFooter && index == itemCount - 1)){
continue;
}
index -= mIsHeader ? 1 : 0;
int newItemCount = itemCount - (mIsHeader ? mIsFooter ? 2 : 1 : 0);
int spanIndex = getSpanIndex(parent, itemView);
int lastRowNum = newItemCount % spanCount;//最后一行余数
boolean isStartColumn = spanIndex == 0;//第一列
boolean isLastColumn = spanIndex == spanCount - 1;//最后一列
boolean isStartRow = index < spanCount;//第一行
boolean isLastRow = newItemCount - index <= (lastRowNum == 0 ? spanCount : lastRowNum);//最后一行
//绘制行间距颜色
left = isStartColumn ? 0 : (mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0)
+ ((mIsWidthSame ? mSpacingWidth : mSpacingHeight) * (spanIndex - 1))
+ itemView.getWidth() * spanIndex;
top = itemView.getTop() + itemView.getHeight();
right = left + 0.5f + itemView.getWidth() + (isStartColumn ? mShowLeft ?
mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0 : mSpacingHeight)
+ (isLastColumn && mShowRight ? mMarginRight > 0 ? mMarginRight : mSpacingWidth : 0);
bottom = top + (isLastRow ? itemView.getBottom() : mSpacingHeight);
c.drawRect(left, top, right, bottom, mPaint);
//绘制第一行顶部间距颜色
if (isStartRow && mShowTop && itemView.getTop() > 0) {
View headerView = parent.getChildAt(0);
int hTop = headerView.getTop();
int hHeight = headerView.getHeight();
c.drawRect(left, mIsHeader && hTop <= 0 ? hTop + hHeight : 0, right, itemView.getTop(), mPaint);
}
//绘制列间距颜色
leftL = isStartColumn ? 0 : (mShowLeft ? mMarginLeft > 0 ? mMarginLeft : mSpacingWidth : 0)
+ ((mIsWidthSame ? mSpacingWidth : mSpacingHeight) * (spanIndex - 1))
+ itemView.getWidth() * spanIndex;
topL = itemView.getTop();
rightL = leftL + (isStartColumn ? mShowLeft ? mMarginLeft > 0 ? mMarginLeft
: mSpacingWidth : 0 : mIsWidthSame ? mSpacingWidth : mSpacingHeight);
bottomL = topL + itemView.getHeight();
c.drawRect(leftL, topL, rightL, bottomL, mPaint);
//绘制最后一列列间距
if (isLastColumn && mShowRight)
c.drawRect(leftR = rightL + itemView.getWidth(), topL, leftR + dp2px(1)
+ (mMarginRight > 0 ? mMarginRight : mSpacingWidth), bottomL, mPaint);
}
break;
case SINGLE_LINE_MULTI_ROW://单行多列 (不支持Header、Footer)
for (int i = 0; i < childCount; i++) {
View itemView = parent.getChildAt(i);
int index = parent.getChildLayoutPosition(itemView);
left = right + (i == 0 ? 0 : itemView.getWidth());
top = mShowTop ? mMarginTop > 0 ? mMarginTop : mIsMarginSame ? mSpacingWidth : mSpacingHeight : 0;
right = itemView.getLeft();
bottom = top + itemView.getHeight();
c.drawRect(left, top, right, bottom, mPaint);
//绘制最后一列列间距
if (index == itemCount - 1) {
right = right + itemView.getWidth();
c.drawRect(right, top, right + (mShowRight ? mMarginRight > 0 ?
mMarginRight : mSpacingWidth : 0), bottom, mPaint);
}
if (mShowTop || mShowBottom) {
leftL = rightL;
topL = itemView.getHeight() + itemView.getTop();
rightL = itemView.getWidth() + itemView.getLeft();
rightL = rightL + (index == itemCount - 1 && mShowRight ? mMarginRight > 0 ? mMarginRight : mSpacingWidth : 0);
bottomL = mIsMarginSame ? mSpacingWidth : mSpacingHeight;
//绘制顶部间距
if (mShowTop) c.drawRect(leftL, 0, rightL, mMarginTop > 0 ? mMarginTop : bottomL, mPaint);
//绘制底部间距
if (mShowBottom) c.drawRect(leftL, topL, rightL, topL +
(mMarginBottom > 0 ? mMarginBottom : bottomL), mPaint);
}
}
break;
}
}
/**
* 周边是否有间距 (右、下、左、上)
* @param showLeft
* @param showTop
* @param showRight
* @param showBottom
* @return
*/
public DividerItemDecoration showAround(boolean showLeft, boolean showTop, boolean showRight, boolean showBottom){
mShowLeft = showLeft;
mShowTop = showTop;
mShowRight = showRight;
mShowBottom = showBottom;
return this;
}
/**
* 设置列表上下左右间距
* @param marginLeft
* @param marginTop
* @param marginRight
* @param marginBottom
* @return
*/
public DividerItemDecoration setMargins(float marginLeft, float marginTop, float marginRight, float marginBottom){
showAround(marginLeft > 0, marginTop > 0, marginRight > 0, marginBottom > 0);
mMarginTop = dp2px(marginTop);
mMarginRight = dp2px(marginRight);
mMarginBottom = dp2px(marginBottom);
mMarginLeft = dp2px(marginLeft);
return this;
}
/**
* 上下边距是否与左右边距相同
* @return
*/
public DividerItemDecoration setMarginSame(boolean isMarginSame){
mIsMarginSame = isMarginSame;
return this;
}
/**
* 多行多列,列宽是否与左右边距相同
* @return
*/
public DividerItemDecoration setWidthSame(boolean isWidthSame){
mIsWidthSame = isWidthSame;
return this;
}
/**
* 是否有Header
* @param isHeader
* @return
*/
public DividerItemDecoration setIsHeader(boolean isHeader){
mIsHeader = isHeader;
return this;
}
/**
* 是否有Footer
* @param isFooter
* @return
*/
public DividerItemDecoration setIsFooter(boolean isFooter){
mIsFooter = isFooter;
return this;
}
/**
* 设置分割线颜色
* @param spacingColor
* @return
*/
public DividerItemDecoration setSpacingColor(int spacingColor){
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(spacingColor);
mPaint.setStyle(Paint.Style.FILL);
return this;
}
/**
* 设置分割线左右外间距
* @param spacingMarginLeft
* @param spacingMarginRight
* @return
*/
public DividerItemDecoration setSpacingMargin(int spacingMarginLeft, int spacingMarginRight){
mSpacingMarginLeft = dp2px(spacingMarginLeft);
mSpacingMarginRight = dp2px(spacingMarginRight);
return this;
}
/**
* 获取「多行多列」的总列数
* @param parent
* @return
*/
private int getSpanCount(RecyclerView parent) {
int spanCount = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
else if (layoutManager instanceof StaggeredGridLayoutManager){
spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
}
return spanCount;
}
/**
* 获取「多行多列」当前列的索引
* @param parent
* @param view
* @return
*/
private int getSpanIndex(RecyclerView parent, View view){
int spanIndex = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
spanIndex = ((GridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
else if (layoutManager instanceof StaggeredGridLayoutManager){
spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
}
return spanIndex;
}
/**
* dp转px
* @param dp
* @return
*/
private int dp2px(float dp){
float density = mContext.getResources().getDisplayMetrics().density;
return (int) (density * dp);
}
}
使用 (多行单列)
//DividerItemDecoration(上下文,行间距,类型);
//MULTI_LINE_SINGLE_ROW 多行单列,无Header
DividerItemDecoration decoration = new DividerItemDecoration(this, 10, DividerItemDecoration.MULTI_LINE_SINGLE_ROW);
rvList.addItemDecoration(decoration);
多行单列,支持Header
//MULTI_LINE_SINGLE_ROW_HF 多行单列,支持Header
DividerItemDecoration decoration = new DividerItemDecoration(this, 10, DividerItemDecoration.MULTI_LINE_SINGLE_ROW_HF);
//支持Footer
decoration.setIsFooter(true);
多行多列
//MULTI_LINE_MULTI_ROW 多行多列
DividerItemDecoration decoration = new DividerItemDecoration(this, 10, DividerItemDecoration.MULTI_LINE_MULTI_ROW);
//支持Header
decoration.setIsHeader(true);
//支持Footer
decoration.setIsFooter(true);
单行多列,注意此类型不支持 Header、Footer
//SINGLE_LINE_MULTI_ROW 单行多列
DividerItemDecoration decoration = new DividerItemDecoration(this, 10, DividerItemDecoration.SINGLE_LINE_MULTI_ROW);
设置列间距(默认是左右间距)
//DividerItemDecoration(上下文,列间距,行间距,类型);
DividerItemDecoration decoration = new DividerItemDecoration(this, 15, 10, DividerItemDecoration.MULTI_LINE_SINGLE_ROW);
周边是否有间距 (右、下、左、上)
//按(右、下、左、上)顺序设置
decoration.showAround(true, true, true, true);
上下边距是否与左右边距相同
/*
表示上下边距的高与列间距相同
*/
decoration.setMarginSame(true);
多行多列,列宽是否与左右边距相同
//只针对多行多列,表示列间距与左右间距相同
decoration.setWidthSame(true);
设置分割线颜色
decoration.setSpacingColor(0XFFFFFFFF);
decoration.setSpacingColor(R.color.white);
更新记录
- 新增
setMargins()
方法,设置列表上下左右间距,调用该方法无需再要用showAround()
方法。 - 支持StaggeredGridLayoutManager(瀑布流),使用与多行多列用法一致。
注意事项
- 行多列必须自行按照列间距计算并设置Item宽度,否则会出现列间距错误
- 如需要设置Header、Footer间距,需自行设置内边距或外边距
目前不支持StaggeredGridLayoutManager(瀑布流),后期可能更新(以支持)- 若出现其他问题,可以留言或联系我(jjzhujianyu@163.com)
- 转载请说明出处,欢迎大家提出建议