为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>

最终运行结果

  1. 类似ListView的效果
  2. Android RecyclerView中间分割线 recyclerview添加分割线_android

  3. 类似GridView效果
  4. Android RecyclerView中间分割线 recyclerview添加分割线_android_02

问题

当吧shape中的宽和高都改为40dp的时候,明显看到最右边一栏比其他的都要宽一个shape中我们设置的宽度,

Android RecyclerView中间分割线 recyclerview添加分割线_ide_03

源码分析

在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的位置,所以会出现宽度不一样的情况。