关于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,只需要简单的修改即可使用。如果文章有错误,欢迎大家指正!!!