最近一直在研究安卓中幾個常用控制元件的原始碼,希望能通過學習原始碼學習到google大牛在封裝一些複雜view的思想,為以後自己造輪子提供更好的思路.
RecyclerView
是一個使用者可以全面定製的元件,本文將全面分析RecyclerView的各種機制,包括viewholder
複用機制,LayoutManager
佈局機制,ItemAnimator
item動畫等RecyclerView
暴露給使用者的所有可以自定義的部分在原始碼中的體現.RecylerView
完全區別於ListView
,尤其在Item的複用方面,RecyclerView
不在讓使用者關注於Item的複用,讓使用者可以更專注去處理UI上的邏輯.
關於ListView大家可以看我上一篇部落格去了解一下他的Item回收機制.
本文將從以下幾個方面對RecyclerView進行講解
注 : 其中會穿插著對LayoutManager
,ItemAnimator
等使用者自定義元件的分析.
onMeasure
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
...
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
} else {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
}
mState.mInPreLayout = false;
}
可以看到ReyelcerView
的onMeasure
這裡有個判斷
如果mLayout
不為空的時候,會呼叫mLayout.onMeasure(mRecycler,
mState, widthSpec, heightSpec);
進行測量,這個mLayout
其實就是LayoutManager
,負責了RecyelcerView的測量.一般來說如果我們想自定義ReyclerView
的onMeasure
方法,只要在setLayoutManager方法中放入自己的自定義LayoutManger就可以了,系統為我們實現了LinearLayoutManager
用來擺放,而這個類裡面並沒有重寫LayoutManager的onMeasure
方法,所以我們直接檢視LayoutManaer
預設的測量方法看看.
下面我們來通過分析LayoutManager
看一下它是怎麼進行對onMeasure的處理
- LinearLayoutManager的onMeasure
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
它這裡又呼叫了recyclerView的defaultOnMeasure(widthSpec, heightSpec);
預設的measure方法
- mRecyclerView.defaultOnMeasure(widthSpec, heightSpec)
private void defaultOnMeasure(int widthSpec, int heightSpec) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final int widthSize = MeasureSpec.getSize(widthSpec);
final int heightSize = MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
width = ViewCompat.getMinimumWidth(this);
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
height = ViewCompat.getMinimumHeight(this);
break;
}
setMeasuredDimension(width, height);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
很熟悉的程式碼,主要就是對RecyclerView
根據測量模式進行測量,最後通過setMeasuredDimension(width,
height);
給成員變數measuredWidth
和measuredHeight
賦值.可是這時候就會有疑惑,這裡並沒有看到對子view的測量,ListView
在這裡就會對子view進行測量了,為什麼RecyclerView沒有,難道是我們分析錯了嗎?我們接著往下看…..
onLayout
我們看下ReyclerView
的onLayout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
eatRequestLayout();
dispatchLayout();
resumeRequestLayout(false);
mFirstLayoutComplete = true;
}
這裡有個dispatchLayout方法,根據方法名我們可以猜到應該是給子控制元件分發layout事件的方法,我們點進去看看
- dispatchLayout(); 進行佈局的方法
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
return;
}
mState.mItemCount = mAdapter.getItemCount();
....進行些佈局前的初始化操作...
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
....後面是對ItemAnimator動畫的執行,我們後面再講解...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
可以看出dispatchLayout()
最後還是會呼叫mLayout.onLayoutChildren(mRecycler,
mState);
,我們上面說過mLayout
就是我們傳遞入的LayoutManager
,呼叫LayoutManager
的onLayoutChildren
進行佈局,我們去看看LinearLayoutManager
的onLayoutChildren是如何進行佈局的.
- LinearLayoutManager.onLayoutChildren
關於佈局錨點: onLayoutChildren中會先確認佈局錨點mAnchor,然後從佈局錨點為開始位置,以此為起點向開始和結束方向填充ItemView.
mAnchor包含了子控制元件在Y軸上起始繪製偏移量(coordinate),ItemView在Adapter中的索引位置(position)和佈局方向
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
resolveShouldLayoutReverse();
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
...
if (mAnchorInfo.mLayoutFromEnd) {
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
} else {
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
上面的程式碼,會計算mAnchorInfo.mLayoutFromEnd
的值,這個值是通過mAnchorInfo.mLayoutFromEnd
= mShouldReverseLayout ^ mStackFromEnd;
進行計算的, mShouldReverseLayout
的值在resolveShouldLayoutReverse();
中獲取,其中會根據佈局朝向去給mShouldReverseLayout
賦值,如果佈局朝向是VERTICAL,就為false,反之true.mStackFromEnd
是通過public
void setStackFromEnd(boolean stackFromEnd)
方法進行賦值,這個方法需要呼叫者呼叫,我們一般不呼叫,所以為初始值false.所以根據或運算,如果是豎直方向mAnchorInfo.mLayoutFromEnd為false.
得到了佈局方向,就會呼叫相應的邏輯進行佈局,最後填充的方法為fill
.
Item測量,Item佈局
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
recycleByLayoutState(recycler, layoutState);
}
}
fill
方法中核心的填充方法layoutChunk
,他會先從快取中取ViewHolder,如果沒有,就回去建立,之後會將建立好的ViewHolder放入複用集合中.我們先看layoutChunk
如何填充的
這個方法就是核心的佈局方法,layoutState.next(recycler);
是從快取機制從取Item的具體方法,這個我們下面會說到.
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
right - params.rightMargin, bottom - params.bottomMargin);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
}
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.isFocusable();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
這個方法會先從RecyelrVIew快取中View,然後判斷layoutState.mScrapList
是否為null,如果為空,就表示這個view不在移除螢幕的位置,就要進行填充,呼叫addView
方法,將view填充進來,這個方法內部就式呼叫viewGroup
的addView方法.
之後呼叫measureChildWithMargins(view, 0, 0);
對view進行測量,這就是為什麼在RecyerlView的構造方法中沒有看到對子view的測量,原來在這裡測量.
之後呼叫layoutDecorated
對view進行layout佈局, 這個方法裡面就是呼叫了child.layout
方法對控制元件進行佈局.
到了這裡,recycleView的填充就此結束了,所有應該在recycleView可見區域的view就被填充到螢幕上了.
ItemDecoraction
我們一般通過addItemDecoration
對分割線進行繪製,谷歌為我們實現的DividerItemDecoration
,其實內部就算呼叫了系統ListView的分割線樣式進行繪製,在ItemDecoration
的onDraw方法中繪製分割線,我們就來研究下這個ItemDecoration
在原始碼中的體現.
我們都知道一個view的繪製是通過draw方法開始的,所以我們從draw方法查詢他的痕跡.
@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);
}
...
}
darw方法中呼叫ItemDecoration
的onDrawOver
,可以看到這個方法是在super.draw(c);
執行完畢後呼叫的,根據View的繪製邏輯,在draw
方法呼叫過後,表示系統的繪製流程已經結束了,也就是說這個onDrawOver
是在view的繪製流程全部結束以後呼叫的方法.
如果看過View的繪製流程,我們知道在super.draw(c)
方法中,會呼叫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);
}
}
看到了嗎,這裡才是真正呼叫ItemDecoration
的onDraw
方法的地方,在這個地方,我們就可以呼叫ItemDecoration
的onDraw方法繪製分割線了.
但是還有個方法需要我們注意,如果我們想要自定義Item的間距怎麼辦,我們知道ItemDecoration
中有個getItemOffSets
方法可以自定義間距,那麼這個方法是怎麼發生作用的呢?
根據View的繪製流程,我們猜想,如果要給view新增邊距,那麼一定會在測量view的時候對padding,margin進行賦值,我們回到剛才的核心填充方法layoutChunk
中對Item測量的方法measureChildWithMargins(view,
0, 0);
.
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
....
child.measure(widthSpec, heightSpec);
}
- getItemDecorInsetsForChild
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
這樣是不是感覺豁然開朗了,原來是這裡會呼叫getItemOffsets
拿到間距,這樣就實現了使用者自定義佈局邊距.
ReyclerView的滑動以及ViewHolder機制
如果看過我上一篇ListView
解析,一定會記得ListView
就是在滑動過程中完成的對Item的回收,這裡我們將通過對RecyclerView的滑動講解,進一步的分析ReyclerView中重要的ViewHolder
機制.
在研究原始碼之前,我們先了解下RecyclerView的滑動狀態 : RecyclerView的滑動過程可以分為2個階段,手指在螢幕上移動,使RecyclerView滑動的過程,可以稱為scroll;手指離開螢幕,RecyclerView繼續滑動一段距離的過程,可以稱為fling。
先看scroll過程,手指沒有離開介面,還在滑動過程中
既然還沒有離開介面,那一定在ACITON_MOVE中
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id " +
mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
final int dx = x - mLastTouchX;
final int dy = y - mLastTouchY;
if (scrollByInternal(
canScrollHorizontally ? -dx : 0, canScrollVertically ? -dy : 0)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
mLastTouchX = x;
mLastTouchY = y;
} break;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
這個方法中會進行一些對滑動的判斷,只要滑動有效,就會呼叫scrollByInternal
方法.
boolean scrollByInternal(int x, int y) {
int overscrollX = 0, overscrollY = 0;
int hresult = 0, vresult = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
eatRequestLayout();
mRunningLayoutOrScroll = true;
if (x != 0) {
hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
overscrollX = x - hresult;
}
if (y != 0) {
vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState);
overscrollY = y - vresult;
}
...
}
....
return hresult != 0 || vresult != 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
可以看到這裡通過對滑動方向的判斷呼叫相對應的滑動方法,如果是我們是垂直滑動的話,會呼叫mLayout.scrollVerticallyBy(y, mRecycler, mState);
方法,也就說具體的滑動邏輯也是由LayoutManager
處理的.
我們來看看LinearLayoutManager
的scrollVerticallyBy
方法
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
呼叫scrollBy(dy, recycler, state);
方法
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
mLayoutState.mRecycle = true;
ensureLayoutState();
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
final int freeScroll = mLayoutState.mScrollingOffset;
final int consumed = freeScroll + fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
if (DEBUG) {
Log.d(TAG, "Don't have any more elements to scroll");
}
return 0;
}
final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
}
return scrolled;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
看到了嗎,這個方法中,會呼叫fill方法對向前介面的Item進行填充,最後再對Item進行平移,這裡我們回到fill方法
,我們回一下其中的核心填充方法layoutChunk
,其中有這麼一行View
view = layoutState.next(recycler);
,這個方法會實現從RecyclerView的複用機制中獲取View,我們這裡研究下這個方法是怎麼實現的,這個方法內部會呼叫getViewForPosition
方法進行具體的獲取操作.
這裡有這麼幾個需要注意的複用物件 :
<1>scrapped : 從RecyclerView中刪除的view
<2>cached : 是ItemView的一級快取,cached集合的大小預設為2
<3>exCached : 是ItemView的二級快取,exCached是需要我們通過RecyclerView.ViewCacheExtension自己實現的,預設沒有
<4>recycled : 集合其實是一個Map,定義在RecyclerView.RecycledViewPool中將ItemView以ItemType分類儲存了下來,這裡算是RecyclerView設計上的亮點,通過RecyclerView.RecycledViewPool可以實現在不同的RecyclerView之間共享ItemView,只要為這些不同RecyclerView設定同一個RecyclerView.RecycledViewPool就可以了。
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount());
}
boolean fromScrap = false;
ViewHolder holder = null;
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
if (!dryRun) {
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrap = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount());
}
final int type = mAdapter.getItemViewType(offsetPosition);
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder");
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view.");
}
}
}
if (holder == null) {
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
holder = getRecycledViewPool()
.getRecycledView(mAdapter.getItemViewType(offsetPosition));
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this,
mAdapter.getItemViewType(offsetPosition));
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
mAdapter.bindViewHolder(holder, offsetPosition);
attachAccessibilityDelegate(holder.itemView);
bound = true;
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
return holder.itemView;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
上面註釋的已經十分清楚,就是從RecyclerView的幾個快取中去獲取,一級一級向下取,最後如果沒有,這時候會呼叫Adapter
的createViewHolder
來建立ViewHolder,這個就是自己建立的ViewHolder,最後這個方法返回ViewHolder中存放的ItemView
就拿到了每個Item的View物件.
如果有向快取中取,那麼一定有存,我們來看看這個複用機制是怎麼存的,在fill
中,有這麼一個方法在layoutChunk
執行之後,recycleByLayoutState
,這個方法向裡面一直呼叫,最終會有個recycleView
方法,看名字真有那麼點意思,裡面最後呼叫了recycleViewHolderInternal
方法,這個就是RecyclerView最終呼叫的新增方法,我們一起來分析看看.
- recycleViewHolderInternal
void recycleViewHolderInternal(ViewHolder holder) {
...
if (forceRecycle || holder.isRecyclable()) {
boolean cached = false;
if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) &&
!holder.isChanged()) {
final int cachedViewSize = mCachedViews.size();
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
}
} else if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are stil removing it from animation lists");
}
mState.onViewRecycled(holder);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
這個註釋分析的很清楚,就是對ViewHolder的一個儲存過程.到這裡,就將RecyclerView
的複用機制分析完成.
RecyclerView動畫
RecyclerView定義了4種針對資料集的操作,分別是ADD、REMOVE、UPDATE、MOVE,封裝在了AdapterHelper.UpdateOp類中,並且所有操作由一個大小為30的物件池管理著。當我們要對資料集作任何操作時,都會從這個物件池中取出一個UpdateOp物件,放入一個等待佇列中,最後呼叫
RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,根據這個等待佇列中的資訊,對所有子控制元件重新測量、佈局並繪製且執行動畫。以上就是我們呼叫Adapter.notifyItemXXX()系列方法後發生的事。
我們以remove操作為例 :
public final void notifyItemRemoved(int position) {
mObservable.notifyItemRangeRemoved(position, 1);
}
呼叫被觀察者的notifyItemRangeRemoved方法,我們到RecyclerView的被觀察者AdapterDataObservable
中看看.
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
這裡呼叫了觀察者RecyclerViewDataObserver
的onItemRangeRemoved
的方法
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
這裡我們又回到了onLayout方法中,還記得我們之前省略過一部分嗎,就是在那裡實現的
void dispatchLayout() {
... 上面為填充方法,已經分析過....
if (mState.mRunSimpleAnimations) {
int preLayoutCount = mState.mPreLayoutHolderMap.size();
for (int i = preLayoutCount - 1; i >= 0; i--) {
ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i);
if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) {
ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i);
mState.mPreLayoutHolderMap.removeAt(i);
View disappearingItemView = disappearingItem.holder.itemView;
mRecycler.unscrapView(disappearingItem.holder);
animateDisappearance(disappearingItem);
}
}
...其他動畫操作...
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
這裡呼叫了animateDisappearance
執行removed動畫
private void animateDisappearance(ItemHolderInfo disappearingItem) {
View disappearingItemView = disappearingItem.holder.itemView;
addAnimatingView(disappearingItem.holder);
int oldLeft = disappearingItem.left;
int oldTop = disappearingItem.top;
int newLeft = disappearingItemView.getLeft();
int newTop = disappearingItemView.getTop();
if (oldLeft != newLeft || oldTop != newTop) {
disappearingItem.holder.setIsRecyclable(false);
disappearingItemView.layout(newLeft, newTop,
newLeft + disappearingItemView.getWidth(),
newTop + disappearingItemView.getHeight());
if (DEBUG) {
Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder +
" with view " + disappearingItemView);
}
if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop,
newLeft, newTop)) {
postAnimationRunner();
}
} else {
if (DEBUG) {
Log.d(TAG, "REMOVED: " + disappearingItem.holder +
" with view " + disappearingItemView);
}
disappearingItem.holder.setIsRecyclable(false);
if (mItemAnimator.animateRemove(disappearingItem.holder)) {
postAnimationRunner();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
這裡最後呼叫了mItemAnimator.animateRemove(disappearingItem.holder)
方法,如果自定義過動畫的朋友一定會知道,我們通過整合ItemAnimator
,重寫它的animateXXX(ViewHolder
holder) 中拿到holder.itemView 就能通過這個View進行動畫操作了.
到這裡,所有關於RecyclerView的原始碼機械就到這裡結束了,我們可以看出,RecyclerView相對於ListView有更好的模組化,更加低耦合,讓使用者可以通過它提供的各種介面遊刃有餘的自定義RecyclerView的各種樣式,並且考慮到使用者根本沒有必要對View的複用有所關注所以加入了ViewHolder機制,這種思想真是值得我們深入學習.
後面我還會對ViewPager,Behavor 進行原始碼解析,希望通過這種方式能夠對自定義View有更深入的理解.
轉自:http://blog.csdn.net/hfyd_/article/details/53910631