Android ItemDecoration
01.ItemDecoration的用途
1.1 作用
- 通过设置recyclerView.addItemDecoration(new DividerDecoration(this));来改变Item之间的偏移量或者对Item进行装饰。
- 当然,你也可以对RecyclerView设置多个ItemDecoration,列表展示的时候会遍历所有的ItemDecoration并调用里面的绘制方法,对Item进行装饰。
1.2 RecyclerView.ItemDecoration是一个抽象类
该抽象类常见的方法如下所示:
1
2
3
4
5
6public void onDraw(Canvas c, RecyclerView parent)
装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡
public void onDrawOver(Canvas c, RecyclerView parent)
装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量。
02.addItemDecoration()源码分析
a.通过下面代码可知,mItemDecorations是一个ArrayList,我们将ItemDecoration也就是分割线对象,添加到其中。
- 可以看到,当通过这个方法添加分割线后,会指定添加分割线在集合中的索引,然后再重新请求 View 的测量、布局、(绘制)。注意: requestLayout会调用onMeasure和onLayout,不一定调用onDraw!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public void addItemDecoration(ItemDecoration decor) {
addItemDecoration(decor, -1);
}
// 主要看这个方法
public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ " layout");
}
if (mItemDecorations.isEmpty()) {
setWillNotDraw(false);
}
if (index < 0) {
mItemDecorations.add(decor);
} else {
// 指定添加分割线在集合中的索引
mItemDecorations.add(index, decor);
}
markItemDecorInsetsDirty();
// 重新请求 View 的测量、布局、绘制
requestLayout();
}b.接着看下markItemDecorInsetsDirty这个方法做了些什么
- 这个方法先获取所有子View的数量,然后遍历了 RecyclerView 和 LayoutManager 的所有子 View,再将其子 View 的 LayoutParams 中的 mInsetsDirty 属性置为 true,最后调用了 mRecycler.markItemDecorInsetsDirty()方法处理复用逻辑。
1
2
3
4
5
6
7
8
9
10
11
12void markItemDecorInsetsDirty() {
final int childCount = mChildHelper.getUnfilteredChildCount();
//先遍历了 RecyclerView 和 LayoutManager 的所有子 View
for (int i = 0; i < childCount; i++) {
final View child = mChildHelper.getUnfilteredChildAt(i);
//将其子 View 的 LayoutParams 中的 mInsetsDirty 属性置为 true
((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
}
//调用了 mRecycler.markItemDecorInsetsDirty(),
//Recycler 是 RecyclerView 的一个内部类,就是它管理着 RecyclerView 的复用逻辑
mRecycler.markItemDecorInsetsDirty();
}c.接着看下markItemDecorInsetsDirty()这个方法
- 该方法就是获取RecyclerView 缓存的集合,然后遍历集合得到RecyclerView 的缓存单位是 ViewHolder,获取缓存对象,在获取到layoutParams,并且将其 mInsetsDirty 字段一样置为 true
1
2
3
4
5
6
7
8
9
10
11
12
13
14void markItemDecorInsetsDirty() {
//就是 RecyclerView 缓存的集合
final int cachedCount = mCachedViews.size();
for (int i = 0; i < cachedCount; i++) {
//RecyclerView 的缓存单位是 ViewHolder,获取缓存对象
final ViewHolder holder = mCachedViews.get(i);
//获得 LayoutParams
LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
if (layoutParams != null) {
//将其 mInsetsDirty 字段一样置为 true
layoutParams.mInsetsDirty = true;
}
}
}d.回过头在看看addItemDecoration中requestLayout方法
- requestLayout 方法用一种责任链的方式,层层向上传递,最后传递到 ViewRootImpl,然后重新调用 view 的 measure、layout、draw 方法来展示布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}e.在 RecyclerView 中搜索 mItemDecorations 集合
- 在onDraw中
1
2
3
4
5
6
7
8@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);
}
}- 在draw方法中
1
2
3
4
5
6
7
8
9
10@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);
}
//省略部分代码
}- 总结概括
- 可以看到在 View 的以上两个方法中,分别调用了 ItemDecoration 对象的 onDraw onDrawOver 方法。
- 这两个抽象方法,由我们继承 ItemDecoration 来自己实现,他们区别就是 onDraw 在 item view 绘制之前调用,onDrawOver 在 item view 绘制之后调用。
- 所以绘制顺序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。