- 更多分享请看:http://www.cherylgood.cn
Google 官方解释
-
An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.
-
All ItemDecorations are drawn in the order they were added, before the item views (in onDraw() and after the items (in onDrawOver(Canvas, RecyclerView, RecyclerView.State).
个人理解:
大致意思是:
-
ItemDecoration 允许应用程序从适配器的数据集中为制定的 view 添加制定的图形和布局偏移量。该特性一般被用于在两个 item 之间绘制分割线,高亮度以及视觉分组等等。
-
所有的 ItemDecorations 都按照它们被添加的顺序在 item 被绘制之前(在 onDraw 方法中)和在 Items 被绘制之后(在 onDrawOver(Canvas,RecyclerView,RecyclerView.State))进行绘制。
可以看到,ItemDecoration 是相当强大和灵活的。
method 学习:
getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
This method was deprecated in API level 22.0.0. Use getItemOffsets(Rect, View, RecyclerView, State)
- 该方法在 API 22.0.0 之后已被废弃,我们可以看代替的方法
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
Retrieve any offsets for the given item.
- 我们可以通过该方法中的 outRect 来设置 item 的 padding 值。比如你要在 item 底部添加一条分割线,此时为了不影响 item 原来的布局参数,我们一般会返回一个地步 padding 为某个 pd 的 outRect,在 recyclerview 绘制 item 的时候会讲该布局数据加入,我们原来的 item 就会多出一个底部 padding,是不是解耦的很完美呢?
onDraw(Canvas c, RecyclerView parent)
_This method was deprecated in API level 22.0.0. Override onDraw(Canvas, RecyclerView, RecyclerView.State) _
- 该方法也已经过期了,看下面的
onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
- 该方法会在绘制 item 之前调用,也就是说他的层级是在 item 之下的,通过该方法,我们可以爱绘制 item 之前绘制我们需要的内容。
onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
- 该方法已过期,看下面的
onDrawOver(Canvas c, RecyclerView parent)
_This method was deprecated in API level 22.0.0. Override onDrawOver(Canvas, RecyclerView, RecyclerView.State) _
- 该方法于 onDrawOver 类似,在绘制 item 之后会调用该方法。
此时,也许你会疑问,他真的是这样执行的么?为了一探究竟,我们来看下源码吧。
@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 的源码中我们可以看到,在 draw 方法中后会遍历 recyclerview 里面的 itemDecoration 然后调用 itemdecoration 的 onDrawOver 方法;而 recyclerview 调用了 super.draw(c)之后会先,父类会先调用 recyclerview 的 onDraw 方法;
@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 里又会调用 itemDecoration 的 onDraw 方法,当 recyclerview 的 onDraw 方法执行完之后,recyclerview 的 draw 方法中 super.draw(c);后面的代码才会继续执行,而 recyclerview 是在绘制了自己之后才会去绘制 item。
-
结论:itemDecoration 的 onDraw 方法在 item 绘制之前调用,itemDecoration 的 onDrawOver 方法在绘制 item 之后调用。
接下来我们在看下 getItemOffsets 这个方法。他真的把我们的 outRect 加到 item 的布局参数里面了么?预知真相,看源码。
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
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;
}
-
首先我们可以看到,getItemOffsets 这个方法在 recyclerview 的 getItemDecorInsetsForChild 中被调用,该方法会把所有的 itemDecortion 中的 rect 累加后返回;我们再看下 getItemDecorInsetsForChild 在哪被调用的。
public void measureChild(View child, int widthUsed, int heightUsed) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
-
在 measureChild 方法中被调用,也就是 recyclerview 在测量 childView 的时候
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
-
使用 margins 测量 childView 时会用到
-
结论,在 getItemOffsets 方法中 outRect 会影响到 recyclerview 中 childView 的布局。
使用 ItemDecoration 实现分割线的都调用过 addItemDecoration 方法。发现,只要调用一次 addItemDecoration 将自定义的分割线 ItemDecoration 添加进去就可以实现分割线效果了,如果我们添加多次会如何呢?
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();
requestLayout();
}
-
从 RecyclerView.addItemDecoration 方法源码可以看到,内部使用了一个 ArrayList 类型的 mItemDecorations 存储我们添加的所有 ItemDecoration。markItemDecorInsetsDirty 方法有什么用呢?我们看下源码
void markItemDecorInsetsDirty() { final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { final View child = mChildHelper.getUnfilteredChildAt(i); ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; } mRecycler.markItemDecorInsetsDirty(); }
-
里面有一个 mInsetsDirty 被重置为 true,最终调用 mRecycler.markItemDecorInsetsDirty();我们继续看 mRecycler.markItemDecorInsetsDirty();方法源码:
void markItemDecorInsetsDirty() {
final int cachedCount = mCachedViews.size(); for (int i = 0; i < cachedCount; i++) { final ViewHolder holder = mCachedViews.get(i); LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); if (layoutParams != null) { layoutParams.mInsetsDirty = true; } } }
-
里面也是将 layoutParams 的 mInsetsDirty 重置为 true,这个 mInsetsDirty 有什么用呢 ?我们继续看源码:
Rect getItemDecorInsetsForChild(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.mInsetsDirty) { return lp.mDeorInsets; } if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { // changed/invalid items should not be updated until they are rebound. 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; }
-
看到这段代码感觉应该是它了,可以看到,
- 判断 childView 的 layoutParams 的 mInsetsDirty 是不是 false 是 false 直接返回 mDecorInsets。
- 判断 itemDecoration 是否已改变或者已不可用,mState.isPreLayout 是 recyclerview 用来处理动画的。
- 如果前面的都不是,就会从新调用 itemDecoration 的 getItemOffsets 方法,重新计算 layout 偏离值之后返回。
-
出于性能的考虑,如果之前为 ChildView 生成过 DecorInsets,那么会缓存在 ChildView 的 LayoutParam 中(mDecorInsets), 同时为了保证 mDecorInsets 的时效性,还同步维护了一个 mInsetsDirty 标记在 LayoutParam 中
-
在获取 ChidlView 的 DecorInsets 时,如果其 mInsetsDirty 为 false,那么代表缓存没有过期,直接返回缓存的 mDecorInsets。
-
如果 mInsetsDirty 为 true,表示缓存已过期,需要根据 ItemDecoration 集合重新生成
- 添加或者删除 ItemDecoration 的时候,会将所有 ChildView 包括 Recycler 中的 mInsetsDirty 设置为 true 来使 DecorInsets 缓存失效
总结:其实 getItemDecorInsetsForChild 方法我们之前在本章前面有分析到。他就是在测量 childView 的时候会调用,所以如果我们的 itemDecortion 中途需要更新,我们需要调用 markItemDecorInsetsDirty 方法,然后调用 requestLayout 请求重新绘制,这样在重新绘制 childView 的时候,就会重新计算 ItemDecortion 中返回的 layout 偏离值。达到我们想要的效果。
- 更多分享请看:http://www.cherylgood.cn
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于