现在自己所写的项目中用到列表的地方基本全部使用的是RecyclerView,而使用列表就会有非常大的几率用到分割线功能。以前在用到RecyclerView的分割线功能的时候,都是利用RecyclerView自身设置的背景色和每个Item的背景色的差异,然后继承RecyclerView.ItemDecoration,并且简单的重写
getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
方法就能达到效果了。简单实现一个分割线效果代码如下
public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
switch (mOrientation) {
case LinearLayoutManager.HORIZONTAL:
outRect.right += mSpace;
if (parent.getChildAdapterPosition (view) == 0)
outRect.left += mSpace;
break;
case LinearLayoutManager.VERTICAL:
outRect.bottom += mSpace;
if (parent.getChildAdapterPosition (view) == 0)
outRect.top += mSpace;
break;
}
}
这样就能实现最简单的一个分割线的效果了。可是今天在写项目的时候遇到了分割线颜色可能会变动的情况。因此上面这种简单的方式就不能满足要求了。
首先,Google了一番发现,原来ItemDecoration能做的事远远不止这么简单,它还能定制出更多的酷炫的效果,不仅能在“Item下面”绘制分割线效果,还能在“Item上面”盖章。要定制更多的效果就要重写ItemDecoration的另外两个方法
onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
和
onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state)
onDraw方法要比Item 的onDraw方法先执行,所以它画出的东西总是在Item的下面,在 onDraw 为 divider 设置绘制范围,并绘制到 canvas 上,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,但由于 decoration 是绘制在 childView 的底下,所以并不可见,但是会存在overDraw。而onDrawOver 方法是在Item的onDraw执行之后才执行,所以onDrawOver画出的东西是在Item的上面。仔细学习了一下onDrawOver方法,简直像发现了新大陆一样。此次项目中,有个列表左边需要展示一张图片,中间是4行文字描述,然后在右边还是一张图片,不过这张图片要有盖在文字上的效果类似于下面这样
我原本是用xml布局文件拼凑了这个效果。但是,仔细研究了onDrawOver方法后,发现用onDrawOver也可以很好的实现此效果。xml文件代码行数固然减少了很多而且加载效率肯定也有所提升,不过最重要的是感觉B格瞬间上升了很大一截
下面先放上自定义ItemDecoration的完整代码
public class MyItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mSize;
private int mOrientation;
private Paint mPaint;
private Bitmap bitmap;
public MyItemDecoration (Context context, int color, int drawableId, int size, int orientation) {
mDivider = new ColorDrawable (color);
mSize = size;
mOrientation = orientation;
bitmap = BitmapFactory.decodeResource (context.getResources (), drawableId);
mPaint = new Paint ();
mPaint.setColor (Color.RED);
mPaint.setStyle (Paint.Style.FILL);
}
@Override
public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
switch (mOrientation){
case LinearLayoutManager.HORIZONTAL:
outRect.right += mSize;
break;
case LinearLayoutManager.VERTICAL:
outRect.bottom += mSize;
break;
}
}
@Override
public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state) {
int left, right, top, bottom;
if (mOrientation == LinearLayoutManager.VERTICAL) {
left = parent.getPaddingLeft ();
right = parent.getWidth () + parent.getPaddingRight ();
int count = parent.getChildCount ();
for (int i = 0; i < count; i++) {
View child = parent.getChildAt (i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams ();
top = child.getBottom () + params.bottomMargin;
bottom = top + mSize;
mDivider.setBounds (left, top, right, bottom);
mDivider.draw (c);
}
}
}
@Override
public void onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state) {
int left, top;
if (mOrientation == LinearLayoutManager.VERTICAL) {
int childCount = parent.getChildCount ();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt (i);
left = child.getWidth () - bitmap.getWidth () - 15;
top = child.getBottom () / 2 + (child.getBottom () / 2 * i) / (i + 1) - bitmap.getHeight () / 2;
c.drawBitmap (bitmap, left, top, mPaint);
}
}
}
}
下面是自己测试的实现效果
感觉很完美,右边“异常”标签的位置也可以通过计算调整。
在自己摸索着写这个MyItemDecoration 的过程中,遇到了个问题一并记录一下。以前都是简单的用,并没有深入的了解过ItemDecoration,也算给自己提个醒,以后不管学习还是工作,尽量多多的去延伸知识面
问题:只重写onDraw方法也可以实现分割线的效果,那么getItemOffsets 的作用是又是什么呢?
在遇到这个问题的时候,我觉得我占了一点好运气,因为我给每个Item都设置了一个边框,如果没有这个边框,可能我会以为只重写onDraw的效果就是正确的,因为它“看起来”的效果确实是正确的。只重写onDraw方法的时候出现的效果是这样的
有分割线的效果,但是边框却“越过”了分割线和下一个Item相连了,于是,我又重写getItemOffsets方法,此时的效果就正常了,如下
可是,为什么会出现“越过”分割线的效果呢?经过我自己多次对照代码运行测试后发现,画第一个分割线的开始位置是这样计算的
public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state) {
......
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt (i);
top = child.getBottom () + params.bottomMargin;
bottom = top + mSize;
......
}
取得Item1.getBottom()作为第一条分割线的top,然后让top加上设置的分割线的高度mSize作为第一条分割线的bottom,余下的分割线以此类推,此时不重写getItemOffsets的情况下,画个草图更加一目了然
虽然通过onDraw方法画出了分割线的效果,但是由于没有重写getItemOffsets()方法,所以RecyclerView的每个Item还是按照原始的布局位置设置,从3个红色箭头也可以看出
图片顶部到Item1 top的距离 > 图片到Item(x) top的距离,(x=2,3,4...)
这样从Item2开始每个Item的顶部都少了一截mSize大小的高度,这就也解释了为什么会出现边框“越过”分割线的原因