为RecyclerView添加分割线
由于RecyclerView并没有支持divider这样的属性,所以就需要我们自己去实现。
1. 给Item的布局去设置margin去实现
2. 自由去画分割线
这里主要实现第二种
创建类继承及RecyclerView.ItemDecoration
public class MyItemDecoration extends RecyclerView.ItemDecoration {
// 通过画笔去绘制我们自己的分割线
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
}
添加构造函数
private Drawable mDivider;
// 传入我们自己定义的 drawableId
public MyItemDecoration(Context context, int drawableResId) {
mDivider = ContextCompat.getDrawable(context,drawableResId);
if (mDivider == null){
throw new IllegalArgumentException("can not init mDivider Drawable by drawableResId ");
}
}
在onDraw()方法中添加如下代码
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// 绘制我们的水平分割线
drawHorizontal(c,parent);
//绘制我们的数值分割线
drawVertical(c,parent);
}
继续
/**
* 绘制水平分割线
*/
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
canvas.save();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View childView = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
// 将 线画在每个childView的底部, 根据View的位置计算分割线的位置
// 子View的底部 + 子View的 bottomMargin 值 以下同理
int top = childView.getBottom() + layoutParams.bottomMargin ;
int bottom =top+mDivider.getIntrinsicHeight();
int right =childView.getRight()+layoutParams.rightMargin+mDivider.getIntrinsicWidth();
int left = childView.getLeft()-layoutParams.leftMargin ;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
/**
* 绘制竖直分割线
*/
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View childView = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
// 将 线画在每个childView的右边
int top = childView.getTop() -layoutParams.topMargin ;
int bottom =childView.getBottom() +layoutParams.bottomMargin ;
int left = childView.getRight()+ layoutParams.rightMargin ;
int right =left+mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
上面重要是绘制的代码,下面来分析getItemOffsets()方法;
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,RecyclerView.State state) {
int right = mDivider.getIntrinsicWidth();
int bottom = mDivider.getIntrinsicHeight();
// 判断当前view是否是最后一行
if (isLastRow(view,parent)){
bottom = 0 ;
}
// 判断当前view是否是最后一列
if (isLastColumn(view,parent)){
right = 0 ;
}
// 为分割线预留的位置,在测量子View的时候使用
outRect.bottom = bottom;
outRect.right = right;
}
判断当前view是否是最后一行 isLastRow(view,parent)
/**
* 是否是最后一行
*/
private boolean isLastRow(View view, RecyclerView parent) {
//当前子View的位置
int currentPosition = ((RecyclerView.LayoutParams)
view.getLayoutParams()).getViewLayoutPosition();
//总的子View个数
int itemCount = parent.getAdapter().getItemCount();
//列数
int spanCount = getSpanCount(parent);
//总的行数 9/3 行数为3 9/2 行数为 5
int totalRow = itemCount % spanCount == 0 ? itemCount / spanCount :(itemCount / spanCount)+1 ;
return (currentPosition+1) > (totalRow -1 )* spanCount;
}
判断当前view是否是最后一列 isLastColumn(view,parent)
/**
* 是否是最后一列
*/
private boolean isLastColumn(View view, RecyclerView parent) {
//当前子View的位置
int currentPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
//列数
int spanCount = getSpanCount(parent);
return (currentPosition+1) % spanCount == 0;
}
得到总的列数
private int getSpanCount( RecyclerView parent){
// 列数
int spanCount = 1;
LayoutManager layoutManager = parent.getLayoutManager();
//判断LayoutManager,得到总列数
if (layoutManager instanceof GridLayoutManager){
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager){
spanCount = ((StaggeredGridLayoutManager) layoutManager)
.getSpanCount();
}
return spanCount;
}
自定义drawable
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" > // 设置形状为矩形
<size android:height="50dp" android:width="50dp"/> 宽度和高度
//渐变色
<gradient android:startColor="@android:color/holo_blue_bright" 开始颜色
android:endColor="@android:color/holo_blue_dark" 结束颜色
android:centerColor="@android:color/holo_blue_light" 中间色
/>
</shape>
最终运行结果
- 类似ListView的效果
- 类似GridView效果
问题
当吧shape中的宽和高都改为40dp的时候,明显看到最右边一栏比其他的都要宽一个shape中我们设置的宽度,
源码分析
在RecyclerView中测量子View的代码中
public void measureChild(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//重点
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
// 最终取出我们在getItemOffsets()方法中设置的left,top ,right,bottom
// 得到这些值后再去测量子View的宽度和高度,在getChildMeasureSpec()方法中取得宽度和高度值实际上是减去了 widthUsed 和 heightUsed 值的,
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
// 测量的时候会考虑分割线的大小,为其预留一定的位置,子View的宽高相应的减少
child.measure(widthSpec, heightSpec);
}
}
这句代码做了什么呢 Rectinsets=mRecyclerView.getItemDecorInsetsForChild(child);
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
// mItemDecorations 我们添加的分割线都存放在这个集合里面
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
//调用我们分割线中重写的方法,将
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
在RecyclerView的onDraw()中是先绘制子View,最后在绘制分割线
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
结论:为分割线预留出来的位置其实是占用了子View的位置,所以会出现宽度不一样的情况。