- 更多分享請看: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