Android进阶之路Android开发经验谈Android技术知识

Android | 一篇文章带你玩转RecyclerView.I

2020-03-14  本文已影响0人  彭旭锐

前言


目录


1. 简介


2. 使用示例

首先,我们使用官方提供的DividerItemDecoration演示ItemDecoration用法,在这里,我们为RecyclerView设置了两条分割线,具体代码如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size android:height="5dp" />
    <solid android:color="#FFFFFF" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size android:height="5dp" />
    <solid android:color="#000000" />
</shape>
val rv: RecyclerView = findViewById(R.id.rv);
rv.layoutManager = LinearLayoutManager(this)
// 添加第一个ItemDecoration
rv.addItemDecoration(DividerItemDecoration(this, VERTICAL).apply {
    setDrawable(ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_divider_1)!!)
})
// 添加第二个ItemDecoration
rv.addItemDecoration(DividerItemDecoration(this, VERTICAL).apply {
    setDrawable(ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_divider_2)!!)
})
rv.adapter = TestAdapter()
效果图

3. API

现在我们关注ItemDecoration提供的三个方法,具体描述如下:

public abstract static class ItemDecoration {

    // 1. 设置ItemView的边距
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
    }
    
    // 2. 在ItemView下层图层绘制,绘制内容会被ItemView遮挡
    public void onDraw(Canvas c, RecyclerView parent, State state) {
    }

    // 3. 在ItemView上层图层绘制,绘制内容会遮挡ItemView
    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
    }
}

3.1 getItemOffsets(Rect outRect,...)

getItemOffsets() 示意图
// RecyclerView

public void measureChild(@NonNull View child, int widthUsed, int heightUsed) {
    RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams();
    // 每个ItemDecoration设置的上、下、左、右边距累加起来
    Rect insets = this.mRecyclerView.getItemDecorInsetsForChild(child);
    widthUsed += insets.left + insets.right;
    heightUsed += insets.top + insets.bottom;
    // 将累加的间距算到ItemView的padding里进行测量
    int widthSpec = getChildMeasureSpec(this.getWidth(), this.getWidthMode(), this.getPaddingLeft() + this.getPaddingRight() + widthUsed, lp.width, this.canScrollHorizontally());
    int heightSpec = getChildMeasureSpec(this.getHeight(), this.getHeightMode(), this.getPaddingTop() + this.getPaddingBottom() + heightUsed, lp.height, this.canScrollVertically());
    if (this.shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
        child.measure(widthSpec, heightSpec);
    }
}

Rect getItemDecorInsetsForChild(View child) {
    RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams();
    if (!lp.mInsetsDirty) {
        return lp.mDecorInsets;
    } else if (this.mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
        return lp.mDecorInsets;
    } else {
        Rect insets = lp.mDecorInsets;
        // 初始化为0
        insets.set(0, 0, 0, 0);
        int decorCount = this.mItemDecorations.size();

        for(int i = 0; i < decorCount; ++i) {
            // 将outRech的上、下、左、右置零
            this.mTempRect.set(0, 0, 0, 0);
            // 依次调用每个ItemDecoration#getItemOffsets()为outRect赋值
            ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).getItemOffsets(this.mTempRect, child, this, this.mState);
            // 每个ItemDecoration设置的上、下、左、右边距累加起来
            insets.left += this.mTempRect.left;
            insets.top += this.mTempRect.top;
            insets.right += this.mTempRect.right;
            insets.bottom += this.mTempRect.bottom;
        }

        lp.mInsetsDirty = false;
        return insets;
    }
}
// DividerItemDecoration
private Drawable mDivider;

public void setDrawable(Drawable drawable) {
    if (drawable == null) {
        throw new IllegalArgumentException("Drawable cannot be null.");
    }
    mDivider = drawable;
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    if (mDivider == null) {
        outRect.set(0, 0, 0, 0);
        return;
    }
    if (mOrientation == VERTICAL) {
        // 纵向布局时,将图片高度作为bottom边距
        outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
    } else {
        // 横向布局时,将图片宽度作为right边距
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
    }
}

3.2 onDraw(Canvas c,...)

onDraw() 示意图
// RecyclerView
@Override
public void onDraw(Canvas c) {
    super.onDraw(c);

    final int count = mItemDecorations.size();
    // 调用每个ItemDecoration的onDraw(...)
    for (int i = 0; i < count; i++) {
        mItemDecorations.get(i).onDraw(c, this, mState);
    }
}

可以看到,RecyclerView#onDraw(...)会调用每个ItemDecoration#onDraw()进行绘制;与getItemOffsets()不同的是,getItemOffsets()是处理每个ItemView的,而onDraw()是针对整个RecyclerView进行绘制

// DividerItemDecoration

private final Rect mBounds = new Rect();

@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);
    }
}

private void drawVertical(Canvas canvas, RecyclerView parent) {
    canvas.save();
    final int left;
    final int right;
    //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
    if (parent.getClipToPadding()) {
        left = parent.getPaddingLeft();
        right = parent.getWidth() - parent.getPaddingRight();
        canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom());
    } else {
        left = 0;
        right = parent.getWidth();
    }
    // RecyclerView的ChildView的个数,ChildView是可见的区域
    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        // 处理每个可见的ChildView
        final View child = parent.getChildAt(i);
        // 获取Item的矩形区域
        parent.getDecoratedBoundsWithMargins(child, mBounds);
        // bottom是矩形区域bottom减去ItemView的translationY
        final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
        // top是bottom减分割线高度
        final int top = bottom - mDivider.getIntrinsicHeight();
        // 设置分割线范围
        mDivider.setBounds(left, top, right, bottom);
        // 绘制分割线
        mDivider.draw(canvas);
    }
    canvas.restore();
}
// 横向省略...

3.3 onDrawOver(Canvas c,...)

onDrawOver() 示意图

4. 示例讲解

Editing...

4.1 万能分割线

4.2 快递时间轴

4.3 联系人分类


推荐阅读


2020 永远不要放弃希望,祝愿大家都能够平安健康!武汉加油!

上一篇下一篇

猜你喜欢

热点阅读