Recyclerview 学习系类之ItemDecoration
- 更多分享请看: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