RecycleView的Item Decoration间隔样式

RecyclerView通过addItemDecoration()方法添加item之间的分割线。Android并没有提供实现好的Divider,因此任何分割线样式都需要自己实现

自定义间隔样式需要继承RecyclerView.ItemDecoration类,该类是个抽象类,官方目前并没有提供默认的实现类,主要有三个方法。

  • onDraw(Canvas c, RecyclerView parent, State state),在Item绘制之前被调用,该方法主要用于绘制间隔样式。
  • onDrawOver(Canvas c, RecyclerView parent, State state),在Item绘制之前被调用,该方法主要用于绘制间隔样式。
  • getItemOffsets(Rect outRect, View view, RecyclerView parent, State state),设置item的偏移量偏移的部分用于填充间隔样式,即设置分割线的宽、高;在RecyclerView的onMesure()中会调用该方法。

onDraw()onDrawOver()这两个方法都是用于绘制间隔样式,我们只需要复写其中一个方法即可。

Google在sample中给了一个参考的实现类:DividerItemDecoration,这里我们通过分析这个例子来看如何自定义Item Decoration。

自定义的间隔样式的实现步骤

  • ① 通过读取系统主题中的 Android.R.attr.listDivider作为Item间的分割线,并且支持横向和纵向。
    该分割线是系统默认的,你可以在theme.xml中找到该属性(android:listDivider)的使用情况。
    如果要设置,则需要在value/styles.xml中设置:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:listDivider">@drawable/item_divider</item>
</style>
  • ② 获取到listDivider以后,该属性的值是个Drawable,在getItemOffsets中,outRect去设置了绘制的范围。
  • onDraw中实现了真正的绘制。

① 获取listDivider

首先看构造函数,构造函数中获得系统属性android:listDivider,该属性是一个Drawable对象。

private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDivider;
public DividerItemDecoration(Context context, int orientation) {
    final TypedArray a = context.obtainStyledAttributes(ATTRS);
    mDivider = a.getDrawable(0);
    a.recycle();
    setOrientation(orientation);
}

② getItemOffsets

接着来看getItemOffsets()的实现:

public void getItemOffsets(Rect outRect, int position, RecyclerView parent) {
    if (mOrientation == VERTICAL_LIST) {
        outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
    } else {
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
    }
}

这里只看mOrientation == VERTICAL_LIST的情况,outRect是当前item四周的间距,类似margin属性,现在设置了该item下间距为mDivider.getIntrinsicHeight()

那么getItemOffsets()是怎么被调用的呢?

RecyclerView继承了ViewGroup,并重写了measureChild(),该方法在onMeasure()中被调用,用来计算每个child的大小,计算每个child大小的时候就需要加上getItemOffsets()设置的外间距:

public void measureChild(View child, int widthUsed, int heightUsed){
    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);//调用getItemOffsets()获得Rect对象
    widthUsed += insets.left + insets.right;
    heightUsed += insets.top + insets.bottom;
    //...
}

③ onDraw

这里我们只考虑mOrientation == VERTICAL_LIST的情况,DividerItemDecoration的onDraw()实际上调用了drawVertical()

public void drawVertical(Canvas c, RecyclerView parent) {
    final int left = parent.getPaddingLeft();
    final int right = parent.getWidth() - parent.getPaddingRight();
    final int childCount = parent.getChildCount();
    // 画每个item的分割线
    for (int i = 0; i < childCount; i++) {
        final View child = parent.getChildAt(i);
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                .getLayoutParams();
        final int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
        final int bottom = top + mDivider.getIntrinsicHeight();
        mDivider.setBounds(left, top, right, bottom);/*规定好左上角和右下角*/
        mDivider.draw(c);
    }
}

那么onDraw()是怎么被调用的呢?还有ItemDecoration还有一个方法onDrawOver(),该方法也可以被重写,那么onDraw()onDrawOver()之间有什么关系呢?

我们来看下面的代码:

class RecyclerView extends ViewGroup{
    public void draw(Canvas c) {
        super.draw(c); //调用View的draw(),该方法会先调用onDraw(),再调用dispatchDraw()绘制children

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        ...
    }
    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的绘制流程,首先调用RecyclerView重写的draw()方法,随后super.draw()即调用View的draw(),该方法会先调用onDraw()(这个方法在RecyclerView重写了),再调用dispatchDraw()绘制children。因此:ItemDecoration的onDraw()在绘制Item之前调用,ItemDecoration的onDrawOver()在绘制Item之后调用。

当然,如果只需要实现Item之间相隔一定距离,那么只需要为Item的布局设置margin即可,没必要自己实现ItemDecoration这么麻烦。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值