- 更多分享请看: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](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#getItemOffsets(android.graphics.Rect, int, android.support.v7.widget.RecyclerView))(Rect outRect, int itemPosition, RecyclerView parent)
This method was deprecated in API level 22.0.0. Use [getItemOffsets(Rect, View, RecyclerView, State)](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#getItemOffsets(android.graphics.Rect, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))
- 该方法在API 22.0.0之后已被废弃,我们可以看代替的方法
[getItemOffsets](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#getItemOffsets(android.graphics.Rect, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))(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](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView))(Canvas c, RecyclerView parent)
*This method was deprecated in API level 22.0.0. Override [onDraw(Canvas, RecyclerView, RecyclerView.State)](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))
- 该方法也已经过期了,看下面的
[onDraw](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))(Canvas c, RecyclerView parent, RecyclerView.State state)
Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
- 该方法会在绘制item之前调用,也就是说他的层级是在item之下的,通过该方法,我们可以爱绘制item之前绘制我们需要的内容。
[onDrawOver](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State))(Canvas c, RecyclerView parent, RecyclerView.State state)
Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
- 该方法已过期,看下面的
[onDrawOver](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView))(Canvas c, RecyclerView parent)
*This method was deprecated in API level 22.0.0. Override [onDrawOver(Canvas, RecyclerView, RecyclerView.State)](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html#onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.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.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; }
-
看到这段代码感觉应该是它了,可以看到,
- 判断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