LowRecyclerView系列之:RecyclerView之添加分割线

关于Recyclerview添加分割线的博客很多,但是很多博客关于分割线都是讲怎么实现的,没有讲原理,而鸿洋大神的关于分割线的文章介绍的很详细,但是感觉有些地方仍然不是太清楚。这篇文章以初学者的角度向大家详细介绍了分割线添加的原理,详细的解读源码,力求让大家能够轻松理解dividers如何添加。同时解析LowRecyclerView的通用分割线的实现。

源码分析

使用过RecyclerView的小伙伴们对recyclerView.addItemDecoration()这个方法很熟悉,没错我们都是通过这个方法来添加分割线的。我们翻阅RecyclerView可以了解他,源码如下:

    /**
     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
     * affect both measurement and drawing of individual item views.
     *
     * <p>Item decorations are ordered. Decorations placed earlier in the list will
     * be run/queried/drawn first for their effects on item views. Padding added to views
     * will be nested; a padding added by an earlier decoration will mean further
     * item decorations in the list will be asked to draw/pad within the previous decoration's
     * given area.</p>
     *
     * @param decor Decoration to add
     */
    public void addItemDecoration(ItemDecoration decor) {

    }

第一段注释的大致意思是说addItemDecoration()这个方法主要是给RecyclerView添加一个ItemDecoration(中文译作Item装饰,我们常说的分割线就是用来装饰我们的Item),Item decorations 他可以影响单个Item视图的measurement(度量)和drawing(绘图),仔细分析这句话的意思就是说我们的分割线是通过改变Item来绘制一定尺寸的分割线。

第二段主要是说:Item Decorations是有序的。在list将要被运行/查询/绘制之前,Decoration会首先改变list的item视图。Decoration将会以嵌套的形式填充到item视图中去;在list被绘制的时候先去将Decoration的空间偏移出来。

通过上面两段我们大致知道Decoration是通过在item里面另外开辟的空间进行绘制的,那么我们怎么才能对分割线的空间大小进行控制呢,在addItemDecoration()方法中有一个ItemDecoration参数,我们接查看他的源码,如下:

 /**
     * An ItemDecoration allows the application to add a special drawing and layout offset
     * to specific item views from the adapter's data set. This can be useful for drawing dividers
     * between items, highlights, visual grouping boundaries and more.
     *
     * <p>All ItemDecorations are drawn in the order they were added, before the item
     * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
     * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
     * RecyclerView.State)}.</p>
     */
    public static abstract class ItemDecoration 

一个ItemDecoration允许一个应用从adapter的adta的集合中添一个特殊的drawing和布局偏移到具体的item视图,他能够被用于给item,highlights,可视化分组边界等绘制dividers(分割线),翻译的有点不通顺,简单的说ItemDecoration可以用于绘制item的dividers。
接下来我们来看一下ItemDecoration中的方法,如下:
这里写图片描述
可以看到共有6个方法其中三个过时的方法,我们不再看,剩下三个方法,我们先来看getItemOffsets()方法:

/**
         * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
         * the number of pixels that the item view should be inset by, similar to padding or margin.
         * The default implementation sets the bounds of outRect to 0 and returns.
         *
         * <p>
         * If this ItemDecoration does not affect the positioning of item views, it should set
         * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
         * before returning.
         *
         * <p>
         * If you need to access Adapter for additional data, you can call
         * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
         * View.
         *
         * @param outRect Rect to receive the output.
         * @param view    The child view to decorate
         * @param parent  RecyclerView this ItemDecoration is decorating
         * @param state   The current state of RecyclerView.
         */
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                    parent);
        }

第一段的意思是:这个方法是用来检索得到的Item的偏移量,outRect是用来指定Item视图的偏移或者margin的像素,这个方法默认实现是outRect将偏移量或者margin设置为0。
第二段的意思是说:如果想ItemDecoration不影响Item视图的位置和大小,应该在outRect方法返回之前设置他的上、下、左、右四个参数的偏移量。
第三段的意思是说如果你想得到当前view在adapter里的data集合的position,请用recyclerView的实例调用getChildAdapterPosition(View view)方法。
通过这三段注释我们已经能够清晰地了解它的使用了吧,哈哈,不得不说研究源码真的很有用,不说废话继续看另一个方法onDraw,源码如下:

/**
         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
         * Any content drawn by this method will be drawn before the item views are drawn,
         * and will thus appear underneath the views.
         *
         * @param c Canvas to draw into
         * @param parent RecyclerView this ItemDecoration is drawing into
         * @param state The current state of RecyclerView
         */
        public void onDraw(Canvas c, RecyclerView parent, State state) {
            onDraw(c, parent);
        }

这个方法将分割线绘制到recyclerView的画布上,这个方法会将分割线在item绘制之前优先绘制出来,分割线会被绘制到item的下面。
我们再来看最后一个方法:

/**
         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
         * Any content drawn by this method will be drawn after the item views are drawn
         * and will thus appear over the views.
         *
         * @param c Canvas to draw into
         * @param parent RecyclerView this ItemDecoration is drawing into
         * @param state The current state of RecyclerView.
         */
        public void onDrawOver(Canvas c, RecyclerView parent, State state) {
            onDrawOver(c, parent);
        }

我们可以看到这个方法跟onDraw方法解释大致相同,唯一的区别就是onDraw是优先于Item绘制,而onDrawOver是在item绘制之后进行绘制的,onDraw绘制的装饰物是在item之下而onDrawOver是在item之上绘制的。可能对于对View研究很透彻的大牛来说,很容易理解,对于刚接触这方面的小伙伴们来说就非常的困难,下面将详细说名这两个方法的区别。
首先来说再Item之前绘制,我们会优先绘制分割线,由于Item的高度和宽度是固定的而我们的分割线是在item的基础上进行偏移的,我们可以在偏移的位置先将分割线绘制出来,然后再绘制Item,而onDrawOver是先绘制Item的,如果我们先绘制出了item那么item的高度就固定了,我们在item的上面绘制分割线的时候,分割线就会在item之上进行绘制,绘制的分割线就会把我们的item给覆盖,这样带来的体验非常不好。

如何添加分割线

我们通过对ItemDecoration详细的分析,大家应该能够理解添加分割线的原理了吧,简单的概述起来就是通过getItemOffsets()方法在让Item进行偏移或者添加margin,然后调用onDraw在我偏移的地方绘制我们的装饰物。
我们以GridView添加分割线为例,首先创建一个类去继承ItemDecoration,在构造方法中传入分割线的颜色,宽度和gridview的列数,如:

    /**
     * 自定义分割线(高和颜色)
     * @param dividerHeight 分割线高度
     * @param dividerColor  分割线颜色
     * @param nunColumns RecyclerView的行数或列数
     */
    public CommonRecyclerDivider(int dividerHeight, int dividerColor, int nunColumns) {
        mDividerHeight = dividerHeight;
        mNunColumns = nunColumns;
        mAdapter = adapter;
//        初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(dividerColor);
        //填充
        mPaint.setStyle(Paint.Style.FILL);
    }

在该方法中我们先初始化画笔的颜色,然后从写getItemOffsets()方法,分别将item底部和右侧添进行偏移,然后判断最后一列右侧不偏移,代码如下:

 /**
     * 设置分割线
     * 先调用此方法(设置完后在执行onDraw方法)
     */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    //获取每个item视图的posiotion
        int itemPosition = parent.getChildAdapterPosition(view);
     if(itemPosition%mNunColumns==mNunColumns-1){
        outRect.set(0, 0, 0, mDividerHeight);
        }else{
        outRect.set(0, 0, mDividerHeight, mDividerHeight);
        }
    }

然后我们在onDraw方法里进行分割线的绘制,代码如下:

/**
     * 绘制分割线
     * getItemOffsets方法调用后会调用此方法
     */
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        //绘制底部
        drawButtom(c, parent);
        //绘制右侧
        drawRigth(c, parent);
    }
    /**
     * 画divider (底部)
     * @param c
     * @param parent
     */
    private void drawButtom(Canvas c, RecyclerView parent) {
        //获取Item的数量
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            //获取对应poisition对应的item
            final View child = parent.getChildAt(i);
            int left = child.getLeft();
            int right = child.getRight() + mDividerHeight;
            int top = child.getBottom();
            int bottom = top + mDividerHeight;
            c.drawRect(left, top, right, bottom, mPaint);
        }
    }
/**
     * 画divider(右边)
     *
     * @param c
     * @param parent
     */
    private void drawRigth(Canvas c, RecyclerView parent) {
        //获取Item的数量
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            //获取对应poisition对应的item
            final View child = parent.getChildAt(i);
            int top = child.getTop();
            int left = child.getRight();
            int bottom = child.getBottom();
            int right = left + mDividerHeight;
            c.drawRect(left, top, right, bottom, mPaint);
        }
    }

如果上面的代码你看不太懂就接和下面的这幅图去理解:
这里写图片描述
通过这张图我想你应该能够理解分割线的绘制了。其他的不管是listview还是瀑布流绘制方法和gridView绘制方法一样,但是由于瀑布流的高度是变化的,所以无法判断哪个Item是最后一列,所以瀑布流添加分割线的时候最后一列的右侧是绘制出来的。

封装通用的分割线

关于通用的分割线原理基本上跟上面讲述的原理基本相同,我们需要根据recyclerView的显示类型进行判断即可,小伙伴们可以参照上述方法进行封装,也可以使用我的开源项目LowRecyclerView中的CommonRecyclerDivider,只需要简单的修改即可使用。如果文章有错误,欢迎大家指正!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值