Recycler.ItemDecoration
最近任务遇到了需要在RecyclerView
中加入分割线的需求,所以尽可能的说一下对ItemDecoration
的理解,之后还骚酷炫操作的时候再进行补充
简介
ItemDecoration
根据官方文档所说,一个ItemDecoration可以允许应用给Adapter数据集中来的特殊列表项Views加特效和布局偏移,这对于在列表项间加分割线,高亮显示,可视化分组都很有用。
我的理解就是加特技,给列表项加特技,一个个加的过程中如果加相同的特技,就起到分隔的作用;如果这几个加一种特技,另外几个加别的特技,就可以起到分组的作用;如果某几个列表项,也就是少量列表项加特技,就可以起到高亮的作用。
特技的本质与canvas类似,由两方面组成:
- 尺寸
- 样式
实现
自定义ItemDecoration
通过继承Recycler.ItemDecoration
实现。
继承需要实现三个方法:
public void onDraw(Canvas c, RecyclerView parent, State state)
public void onDrawOver(Canvas c, RecyclerView parent, State state)
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
前两个方法比较简单,只需要注意绘制时机就可以,调用顺序为:
graph LR
A[ItemDecoration.onDraw] --> B[child itemView.OnDraw]
B --> C[ItemDecoration.onDrawOver]
onDraw
先贴一个DividerItemDecoration中的代码:
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();
}
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, mBounds);
final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
final int top = bottom - mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
这个方法容易理解,首先锁定canvas
,然后根据RecyclerView
的ClipToPadding
属性确定是否裁剪canvas
(默认是裁剪的,可以通过设置为false
不进行裁剪),然后对计算每个child的尺寸,根据这个尺寸再计算分割线的尺寸,最后画出分割线。
getItemOffset
比较有意思的是第三个方法getItemOffsets
,只在这里被调用:
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
方法返回一个Rect
,这个Rect
通过遍历ItemDecorations
列表把每个ItemDecoration
的左上右下边距值累加获得。getItemOffsets
方法的作用就是获取边距Rect
。
解释下chlid.getLayoutParams().mInsetsDirty
这个是一个缓冲机制,用来避免重复计算边距。
而这个getItemDecorInsetsForChild
调用在:
public void measureChild(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(),
getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
child.measure(widthSpec, heightSpec);
}
可以看到这个方法用来将ItemDecoration
的边距值放在child的MeaSureSpec
的padding
中
Demo
class TopicDecoration extends RecyclerView.ItemDecoration {
private final Drawable mDivider;
private final int mSize;
private final int mSizeHeader;
private final int marginHorizontal;
TopicDecoration(Resources resources, int size, int size2, int width) {
mDivider = new ColorDrawable(resources.getColor(R.color.colordddddd));
mSize = size;
mSizeHeader = size2;
marginHorizontal = width;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left;
int right;
int top;
int bottom;
left = parent.getPaddingLeft() + marginHorizontal;
right = parent.getWidth() - parent.getPaddingRight() - marginHorizontal;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
if (i == 0) {
int left2 = parent.getPaddingLeft();
int right2 = parent.getWidth() - parent.getPaddingRight();
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
top = child.getBottom() + params.bottomMargin;
bottom = top + mSizeHeader;
mDivider.setBounds(left2, top, right2, bottom);
mDivider.draw(c);
} else {
final View child = parent.getChildAt(i);
final 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 getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (parent.getChildLayoutPosition(view) == 0) {
outRect.set(0, 0, 0, mSizeHeader);
} else {
outRect.set(0, 0, 0, mSize);
}
}
}