最近一直在研究安卓中幾個常用控件的源碼,希望能通過學習源碼學習到google大牛在封裝一些複雜view的思想,爲以後自己造輪子提供更好的思路.
RecyclerView
是一個用戶可以全面定製的組件,本文將全面分析RecyclerView的各種機制,包括viewholder
複用機制,LayoutManager
佈局機制,ItemAnimator
item動畫等RecyclerView
暴露給使用者的所有可以自定義的部分在源碼中的體現.RecylerView
完全區別於ListView
,尤其在Item的複用方面,RecyclerView
不在讓用戶關注於Item的複用,讓用戶可以更專注去處理UI上的邏輯.
關於ListView大家可以看我上一篇博客去了解一下他的Item回收機制.
本文將從以下幾個方面對RecyclerView進行講解
注 : 其中會穿插着對LayoutManager
,ItemAnimator
等用戶自定義組件的分析.
onMeasure
- RecyclerView的onMeasure方法
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
...
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
} else {
//調用LayoutManager中的方法測量view
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
}
mState.mInPreLayout = false; // clear
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
可以看到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);
}
- 1
- 2
- 3
它這裏又調用了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
很熟悉的代碼,主要就是對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();
//分發Layout事件
dispatchLayout();
resumeRequestLayout(false);
mFirstLayoutComplete = true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
這裏有個dispatchLayout方法,根據方法名我們可以猜到應該是給子控件分發layout事件的方法,我們點進去看看
- dispatchLayout(); 進行佈局的方法
void dispatchLayout() {
//第一次onLayout的時候mAdapter 和 mLayout肯定爲空,所以不會有下面的邏輯,只有當我們調用setAdapter,或者其他第二次
//重繪的方法,纔會繼續下面的邏輯
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;
}
//這裏通過我們傳入的Adapter的getItemCount方法拿到了Item的個數
mState.mItemCount = mAdapter.getItemCount();
....進行些佈局前的初始化操作...
// Step 2: Run layout
//開始layout
mState.mInPreLayout = false;
//具體怎麼佈局,會調用LayoutManager裏面的方法進行佈局
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
可以看出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) {
// layout algorithm: 佈局算法
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
//通過檢查孩子和其他變量,找到錨座標和錨點項目位置 mAnchor爲佈局錨點
//mAnchor包含了子控件在Y軸上起始繪製偏移量(coordinate),ItemView在Adapter中的索引位置(position)和佈局方向(mLayoutFromEnd)
// 2) fill towards start, stacking from bottom , 開始填充, 從底部堆疊 從開始位置開始,向結束爲位置堆疊填充itemView
// 3) fill towards end, stacking from top 結束填充,從頂部堆疊
// 4) scroll to fulfill requirements like stack from bottom. //滾動以滿足堆棧從底部的要求
// create layout state
...
//這個方法會根據LinearLayoutManger構造中傳入的佈局方向給mShouldReverseLayout賦值
//如果是豎直方向(VERTICAL),mShouldReverseLayout爲false
resolveShouldLayoutReverse();
//重置mAnchorInfo
mAnchorInfo.reset();
//得到堆疊方向 mShouldReverseLayout爲false mStackFromEnd默認爲false
//我們假定傳入的方向是垂直方向,所以mAnchorInfo.mLayoutFromEnd = false
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
...
//重要的堆疊Item的方法,根據堆疊方向進行堆疊
//如果是end方向 從底部開始堆疊
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start // 開始填充
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end //結束填充
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
} else {
//如果排列方向是VERTICAL,走這裏
// fill towards end //結束填充
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
//重要的填充方法
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start //開始填充
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
上面的代碼,會計算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佈局
- fill
//具體的填充方法
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//填充核心方法,從複用集合中取ViewHolder
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
//向複用集合中存ViewHolder
recycleByLayoutState(recycler, layoutState);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
fill
方法中核心的填充方法layoutChunk
,他會先從緩存中取ViewHolder,如果沒有,就回去創建,之後會將創建好的ViewHolder放入複用集合中.我們先看layoutChunk
如何填充的
- layoutChunk
這個方法就是核心的佈局方法,layoutState.next(recycler);
是從緩存機制從取Item的具體方法,這個我們下面會說到.
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//獲取ItemView,會先從Scrap中取,如果沒有會從一級緩存,二級緩存取,最後檢查ReyclerViewPool 如果都沒有 就創建ViewHolder
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
//如果mScrapList爲空,就將view填充進去,這個mScrapList就是Item被移除屏幕被緩存起來的集合,如果沒有在mScrapList中
//說明需要添加到RecyerView顯示界面中
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
//addView 就是 viewGroup的addView方法 將子view填充到RecylerView中
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;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
//佈局子view,這個方法裏面調用了child.layout方法,參數就是計算出來的child位置.
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));
}
// Consume the available space if the view is not removed OR changed
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
這個方法會先從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方法查找他的痕跡.
- draw
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
//ItemDecoration 的數量
for (int i = 0; i < count; i++) {
//調用ItemDecoration 的onDrawOver 方法繪製ReyclervView的背景
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
darw方法中調用ItemDecoration
的onDrawOver
,可以看到這個方法是在super.draw(c);
執行完畢後調用的,根據View的繪製邏輯,在draw
方法調用過後,表示系統的繪製流程已經結束了,也就是說這個onDrawOver
是在view的繪製流程全部結束以後調用的方法.
如果看過View的繪製流程,我們知道在super.draw(c)
方法中,會調用onDraw方法進行繪製,這個一般纔是繪製內容的方法,我們找一下有沒有重寫這個方法.
- 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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
看到了嗎,這裏纔是真正調用ItemDecoration
的onDraw
方法的地方,在這個地方,我們就可以調用ItemDecoration
的onDraw方法繪製分割線了.
但是還有個方法需要我們注意,如果我們想要自定義Item的間距怎麼辦,我們知道ItemDecoration
中有個getItemOffSets
方法可以自定義間距,那麼這個方法是怎麼發生作用的呢?
根據View的繪製流程,我們猜想,如果要給view添加邊距,那麼一定會在測量view的時候對padding,margin進行賦值,我們回到剛纔的核心填充方法layoutChunk
中對Item測量的方法measureChildWithMargins(view, 0, 0);
.
- measureChildWithMargins
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//用來修改Item的邊距 ,也就是說在child.measure之前會先設置好邊距
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
....
child.measure(widthSpec, heightSpec);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 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);
//這裏可以通過ItemDecoration 修改邊距
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
這樣是不是感覺豁然開朗了,原來是這裏會調用getItemOffsets
拿到間距,這樣就實現了用戶自定義佈局邊距.
ReyclerView的滑動以及ViewHolder機制
如果看過我上一篇ListView
解析,一定會記得ListView
就是在滑動過程中完成的對Item的回收,這裏我們將通過對RecyclerView的滑動講解,進一步的分析ReyclerView中重要的ViewHolder
機制.
在研究源碼之前,我們先了解下RecyclerView的滑動狀態 : RecyclerView的滑動過程可以分爲2個階段,手指在屏幕上移動,使RecyclerView滑動的過程,可以稱爲scroll;手指離開屏幕,RecyclerView繼續滑動一段距離的過程,可以稱爲fling。
先看scroll過程,手指沒有離開界面,還在滑動過程中
- onTouchEvent
既然還沒有離開界面,那一定在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) {
//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) {
//如果滑動距離大於 閥值得話, scrollState 就爲SCROLL_STATE_DRAGGING
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
final int dx = x - mLastTouchX;
final int dy = y - mLastTouchY;
//滑動 第一階段scroll完成
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
這個方法中會進行一些對滑動的判斷,只要滑動有效,就會調用scrollByInternal
方法.
- 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) {
//調用LayoutManager的scrollHorzontallBy方法
hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
overscrollX = x - hresult;
}
if (y != 0) {
//調用LayoutManager的scrollVerticallyBy方法
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
可以看到這裏通過對滑動方向的判斷調用相對應的滑動方法,如果是我們是垂直滑動的話,會調用mLayout.scrollVerticallyBy(y, mRecycler, mState);
方法,也就說具體的滑動邏輯也是由LayoutManager
處理的.
我們來看看LinearLayoutManager
的scrollVerticallyBy
方法
- scrollVerticallyBy
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
調用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;
//如上文所講到的fill()方法,作用就是向可繪製區間填充ItemView
//,那麼在這裏,可繪製區間就是滑動偏移量!再看方法mOrientationHelper.offsetChildren()作用就是平移ItemView。
//平移ItemView。 這裏就完成了移動
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
看到了嗎,這個方法中,會調用fill方法對向前界面的Item進行填充,最後再對Item進行平移,這裏我們回到fill方法
,我們回一下其中的核心填充方法layoutChunk
,其中有這麼一行View view = layoutState.next(recycler);
,這個方法會實現從RecyclerView的複用機制中獲取View,我們這裏研究下這個方法是怎麼實現的,這個方法內部會調用getViewForPosition
方法進行具體的獲取操作.
- 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就可以了。
//獲取某個位置需要展示的View,先檢查是否有可複用的View,沒有則創建新View並返回。具體過程爲:
//step1 檢查mChangedScrap,若匹配到則返回相應holder
//step2 檢查AttachedScrap,若匹配到且holder有效則返回相應holder
//step3 查mViewCacheExtension,若匹配到則返回相應holder
//step4 檢查mRecyclerPool,若匹配到則返回相應holder
//step5 否則執行Adapter.createViewHolder(),新建holder實例
//step6 返回holder.itemView
//step7 注:以上每步匹配過程都可以匹配position或itemId(如果有stableId)
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
//根據列表位置獲取ItemView,先後從scrapped、cached、exCached、recycled
//集合中查找相應的ItemView,如果沒有找到,就創建(Adapter.createViewHolder()),最後與數據集綁定。
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;
//先後從scrapped、cached、exCached、recycled集合中查找相應的ItemView
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
//檢查mChangedScrap,若匹配到則返回相應holder
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
//檢查AttachedScrap,若匹配到且holder有效則返回相應holder
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle this scrap
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
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);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
//一級緩存,先檢查一級緩存, 如果有就返回viewHolder
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
//二級緩存, 是開發者自定義的緩存, 從二級緩存中拿到ItemView
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) { // fallback to recycler
// try recycler.
// Head to the shared pool.
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
//檢查mRecyclerPool,若匹配到則返回相應holder
holder = getRecycledViewPool()
.getRecycledView(mAdapter.getItemViewType(offsetPosition));
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
//否則執行Adapter.createViewHolder(),新建holder實例
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()) {
// do not update unless we absolutely have to.
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;
//返回holder.itemView
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
上面註釋的已經十分清楚,就是從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()) {
// Retire oldest cached view
final int cachedViewSize = mCachedViews.size();
//首先判斷cachedView 是否滿了 最大mViewCacheMax = 2
////如果已満就從cached集合中移出一個到recycled集合中去,再把新的ItemView添加到cached集合
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
//如果滿了 就從cachedViews中移除一個,
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
//再把新的ItemView添加到cached集合
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
//如果沒有被緩存 緩存到recycleViewPool中
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");
}
// even if the holder is not removed, we still call this method so that it is removed
// from view holder 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
這個註釋分析的很清楚,就是對ViewHolder的一個存儲過程.到這裏,就將RecyclerView
的複用機制分析完成.
RecyclerView動畫
RecyclerView定義了4種針對數據集的操作,分別是ADD、REMOVE、UPDATE、MOVE,封裝在了AdapterHelper.UpdateOp類中,並且所有操作由一個大小爲30的對象池管理着。當我們要對數據集作任何操作時,都會從這個對象池中取出一個UpdateOp對象,放入一個等待隊列中,最後調用
RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,根據這個等待隊列中的信息,對所有子控件重新測量、佈局並繪製且執行動畫。以上就是我們調用Adapter.notifyItemXXX()系列方法後發生的事。
我們以remove操作爲例 :
- notifyItemRemove()
public final void notifyItemRemoved(int position) {
mObservable.notifyItemRangeRemoved(position, 1);
}
- 1
- 2
- 3
調用被觀察者的notifyItemRangeRemoved方法,我們到RecyclerView的被觀察者AdapterDataObservable
中看看.
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
// since onItemRangeRemoved() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
這裏調用了觀察者RecyclerViewDataObserver
的onItemRangeRemoved
的方法
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
//第一步 將 removed信息放到集合中,對象爲UpdateOp
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
void triggerUpdateProcessor() {
if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
//界面重新佈局,也就式調用 RecyclerView的onLayout方法
requestLayout();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
這裏我們又回到了onLayout方法中,還記得我們之前省略過一部分嗎,就是在那裏實現的
- dispatchLayout
void dispatchLayout() {
... 上面爲填充方法,已經分析過....
if (mState.mRunSimpleAnimations) {
//removed動畫
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);
//執行動畫 remove動畫
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
這裏調用了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);
//remove動畫,這就進行了動畫執行,這裏的動畫就是用戶自定義的實現方式
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
這裏最後調用了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?_t_t_t=0.81394347618334)