RecyclerView之ItemDecoration

序言

  1. 本文结合源码讲解RecyclerView的分割线
  2. 更多相关的源码解析见RecyclerView之三级缓存源码解析

正文

一. Decoration

  • RecyclerView提供了一个abstract的静态内部类: ItemDecoration,即分割线(不过我更愿意将其称为Item装饰);我们可以通过继承该类来实现我们想要的效果,一般情况下我们需要实现的有一下三个方法: onDraw()(绘制Item之间的分割线);onDrawOver()(绘制外层的装饰);getItemOffsets()(设置分割线的偏移量);android.support.v7包中也提供了一个实现,即:DividerItemDecoration, 该类的实现也具有参考价值,可以当做一个模板,其只重写了onDraw()方法,即只提供了绘制Item间分割线,使用的也是默认的ListView的分割线,代码如下;可以看出,其还兼容了水平和垂直方向两种布局
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getLayoutManager() == null || mDivider == null) {
        return;
    }
    if (mOrientation == VERTICAL) {
        drawVertical(c, parent);
    } else {
        drawHorizontal(c, parent);
    }
}
  • 如何为RecyclerView设置分割线:直接在外部通过 RecyclerView.addItemDecoration()来设置分割线,该方法有两个addItemDecoration(ItemDecoration decor, int index),addItemDecoration(ItemDecoration decor),但是实际上后者也是调用的前者(addItemDecoration(decor, -1);),所以这里只分析前一个方法;该方法中做的事情主要是用一个 ArrayList 的 mItemDecorations 变量来存储下设置的ItemDecoration,之后requestLayout(),重新进行measure,layout,和draw;源代码如下,其中只是摘取了部分关键代码
public void addItemDecoration(ItemDecoration decor, int index) {
    ...
    
    if (index < 0) {
        mItemDecorations.add(decor);
    } else {
        mItemDecorations.add(index, decor);
    }
    markItemDecorInsetsDirty();
    requestLayout();
}
  • 那么接下来我们的重点就应该放在RecyclerView的draw()过程上了: RecyclerView重写了View的draw()方法,该方法文档中说的是Manually render this view (and all of its children) to the given Canvas(即主动将当前View及其子View呈现给Canvas); RecyclerView在该方法中绘制外层装饰,即onDrawOver()(部分源码如下),需要注意的是,RecyclerView在绘制外层装饰之前,首先调用的是 super.draw(), 该方法是调用View的draw()方法,而在View的draw()方法中会先进行自身的onDraw()和对子View的dispatchDraw(),也就是说在绘制外层装饰之前,实际上已经保证了各Item和各Item之间分割线的绘制,那么很显然onDrawOver()就是在最外层绘制的了,所以最终造成的效果是,Item的滑动并不会导致外层装饰的移动,这种效果的应用有:比如QQ的消息列表右上角,有时候会出现类似于"答题赢钱"之类的图标等(如下示例图片)就可以用这种方法实现,具体的只需要自定义一个ItemDecoration,然后在onDrawOver()方法中实现即可
RecyclerView之ItemDecoration_第1张图片
QQ.png
@Override
public void draw(Canvas c) {
    //先绘制内层
    super.draw(c);

    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
        mItemDecorations.get(i).onDrawOver(c, this, mState);
    }
    ...
}
  • RecyclerView在onDraw()方法中对每个Item的分割线(即内层分割线)进行绘制,代码如下,很简单;需要注意的是,这里是如何为所有的Item绘制分割线的,如下可知,只需要获取到Item的数量即可;但是如果我们想要对最后一个Item不进行分割线的绘制,那么我们可以将下面 i < count改为i < count-1即可
@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);
    }
}
  • 通过以上分析可知,对于所有分割线的绘制,RecyclerView均交给了我们来处理,所以,这也给我们提供了极大的便利来实现自己想要的效果;例如,这里笔者给出一个实现渐变分割线的示例,主要是在onDraw方法中的处理,代码和效果图如下
RecyclerView之ItemDecoration_第2张图片
ItemDecoration.png
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    final int left = parent.getPaddingLeft();
    final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
    final int childSize = parent.getChildCount();
    for (int i = 0; i < childSize; i++) {
        final View child = parent.getChildAt(i);
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
        final int top = child.getBottom() + layoutParams.bottomMargin;
        final int bottom = top + 20;

        //这里通过不同的Item位置来设置不同的颜色达到效果
        int data = (int) (255 * (float)i / childSize);
        paint.setColor(Color.argb(255, data, data, data));
        c.drawRect(left, top, right, bottom, paint);
    }
}

你可能感兴趣的:(RecyclerView之ItemDecoration)