前幾篇學習了RecyclerView的初始化和繪製過程,主要情景都是在靜止狀態下,沒有手動操作,這篇開始就學習在人爲操作下的代碼流程,先從滑動開始
- RecyclerView源碼學習筆記(一)構造函數和setLayoutManager方法
- RecyclerView源碼學習筆記(二)setAdapter
- RecyclerView源碼學習筆記(三)RecycleView的繪製過程onMeasure,onLayout,onDraw
onInterceptTouchEvent
當一個觸摸事件傳遞到RecyclerView的時候,會調用onInterceptTouchEvent方法,判斷當前viewGroup需不需要攔截這個touch事件,如果要攔截就返回true,否則false,onInterceptTouchEvent
的源碼
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mLayoutFrozen) {
// When layout is frozen, RV does not intercept the motion event.
// A child view e.g. a button may still get the click.
return false;
}
if (dispatchOnItemTouchIntercept(e)) {
cancelTouch();
return true;
}
```
final int action = e.getActionMasked();
final int actionIndex = e.getActionIndex();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mIgnoreMotionEventTillDown) {
mIgnoreMotionEventTillDown = false;
}
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
if (mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
// Clear the nested offsets
mNestedOffsets[0] = mNestedOffsets[1] = 0;
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
break;
case MotionEvent.ACTION_POINTER_DOWN:
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
break;
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(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) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(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 = x;
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = y;
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
} break;
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.clear();
stopNestedScroll(TYPE_TOUCH);
} break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
}
}
return mScrollState == SCROLL_STATE_DRAGGING;
}
首先判斷當前RecyclerView有沒有被凍住,如果是則直接返回false,也就是說在frozen的情況下,RecyclerView的item還是可以收到touch事件。接下來調用dispatchOnItemTouchIntercept(e)
方法,查看其返回值,如果是true
,則取消當前touch事件,並返回true
,看一下這個dispatchOnItemTouchIntercept(e)方法做了什麼
private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
final int action = e.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
mActiveOnItemTouchListener = null;
}
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
mActiveOnItemTouchListener = listener;
return true;
}
}
return false;
}
從代碼可以看出,其實就是去調用了所有註冊的OnItemTouchListener
的onInterceptTouchEvent()
方法,如果有一個返回true
,則直接返回true
,這說明OnItemTouchListener
的實現者可以攔截RecyclerView的touch事件不再往下傳遞。我們這裏假設沒有註冊OnItemTouchListener
,代碼繼續往下走。
直接跳到switch部分,我們假設現在的操作是一次從底部往頂部的拖動動作,那麼事件序列,首先是ACTION_DOWN事件,在ACTION_DOWN這個case裏主要記錄了觸摸點的位置,然後調用startNestedScroll方法,這個方法是嵌套滑動體系裏的,讀者有興趣可以另外研究,這裏不再說明。然後就跳出switch,返回值由mScrollState == SCROLL_STATE_DRAGGING
是否成立確定,這裏是false
,因爲我們在點擊之前,RecycleView是靜止的,沒有滑動,所以mScroolState
是SCROLL_STATE_IDLE
。
從onInterceptTouchEvent返回後,會調到onTouchEvent方法,這裏再把源碼貼上來
public boolean onTouchEvent(MotionEvent e) {
if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
return false;
}
if (dispatchOnItemTouch(e)) {
cancelTouch();
return true;
}
if (mLayout == null) {
return false;
}
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
final boolean canScrollVertically = mLayout.canScrollVertically();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
boolean eventAddedToVelocityTracker = false;
final MotionEvent vtev = MotionEvent.obtain(e);
final int action = e.getActionMasked();
final int actionIndex = e.getActionIndex();
if (action == MotionEvent.ACTION_DOWN) {
mNestedOffsets[0] = mNestedOffsets[1] = 0;
}
vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
switch (action) {
case MotionEvent.ACTION_DOWN: {
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
} break;
case MotionEvent.ACTION_POINTER_DOWN: {
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
} break;
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(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) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float xvel = canScrollHorizontally
? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically
? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
} break;
}
if (!eventAddedToVelocityTracker) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
}
這裏會先進到ACTION_DOWN
這個case,這個裏面做的事情也是記錄了座標,調用startNestedScroll
方法。接下來再回到onInterceptTouchEvent
方法,因爲我們的動作是一個拖動的動作,所以在ACTION_DOWN
之後會緊跟着ACTION_MOVE
事件,在onInterceptTouchEvent
的ACTION_MOVE case中主要進行了滑動距離的判斷,如果距離大於touchSlop則認爲是拖動事件,則將mScrollState
設置爲SCROLL_STATE_DRAGGING
,這時候因爲mScrollState
已經設置成SCROLL_STATE_DRAGGING
。所以onInterceptTouchEvent
將會返回true
,這樣touch事件就不會再傳給item
接下來又會調到onTouchEvent
方法,並進入case ACTION_MOVE
,在case ACTION_MOVE
裏最重要的就是如下代碼段
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
首先判斷當前是不是在拖動,是的,然後就是調用scrollByInternal
方法,這個方法內部會根據拖動的距離,添加item到RecyclerView,那麼重點就研究這部分吧(省略部分代碼)
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
eatRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
fillRemainingScrollValues(mState);
if (x != 0) {
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
。。。。
}
做了如下事情
consumePendingUpdateOperations()
將沒有處理的item操作處理完,我們這裏假設他沒有,直接return
接下來直接跳到如下代碼,因爲是縱向滑動,所以y不等於0,然後調到mLayout.scrollVerticallyBy
if (y != 0) {
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
mLayout.scrollVerticallyBy
在LinearLayoutManager的實現主要就是調用了以下方法
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 consumed = mLayoutState.mScrollingOffset
+ 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);
}
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
這裏的dy是上次的點擊位置減去這次的點擊位置,如果大於0說明是從底部往頂部滑。所以這裏layoutDirection
的值就是LayoutState.LAYOUT_END
,updateLayoutState()
方法是在放item前做一些準備工作,代碼如下
private void updateLayoutState(int layoutDirection, int requiredSpace,
boolean canUseExistingSpace, RecyclerView.State state) {
// If parent provides a hint, don't measure unlimited.
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mExtra = getExtraLayoutSpace(state);
mLayoutState.mLayoutDirection = layoutDirection;
int scrollingOffset;
if (layoutDirection == LayoutState.LAYOUT_END) {
mLayoutState.mExtra += mOrientationHelper.getEndPadding();
// get the first child in the direction we are going
final View child = getChildClosestToEnd();
// the direction in which we are traversing children
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
// calculate how much we can scroll without adding new children (independent of layout)
scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();
} else {
...
}
mLayoutState.mAvailable = requiredSpace;
if (canUseExistingSpace) {
mLayoutState.mAvailable -= scrollingOffset;
}
mLayoutState.mScrollingOffset = scrollingOffset;
}
mLayoutState.mInfinite
表示當前RecyclerView對加載的item有沒有數量限制mLayoutState.mExtra
表示提前加載那些還沒有顯示到屏幕上的item,一般是提前一個屏幕的長度,單位是px- 因爲我們這裏
layoutDirection
等於LayoutState.LAYOUT_END
,所以進入第一個if
,先是給mLayoutState.mExtra
加上底部padding的長度,因爲有時候我們會給RecyclerView設置padding,接着找到屏幕上最靠近底部的那個item。 mLayoutState.mItemDirection
的意思是指我們在添加itemview的時候,從adapter去取對應數據的時候,是從頭開始取還是倒着取,我們這裏不做特殊處理,所以是順着取,mLayoutState.mItemDirection = LayoutState.ITEM_DIRECTION_TAIL
mLayoutState.mCurrentPosition
是指當前要添加的item對應到adapter中的位置mLayoutState.mOffset
表示開始畫item的座標位置,單位是像素scrollingOffset
表示的值是屏幕上最後一個item的下邊緣的座標減去RecycleView的下邊緣座標再減去底部padding的值,這個值表示的意思是,如果我們不添加item,那我們還可以滑動多少距離,如下圖,藍色代表RecyclerView,紅色代表item,綠色代表RecyclerView的底部座標減去buttom padding後的座標,scrollingOffse
的值就是第二個item的下邊緣座標減去綠色線的座標的差。
mLayoutState.mAvailable
代表滑動的距離,如果canUseExistingSpace
爲true
,則mLayoutState.mAvailable
再減去scrollingOffset
,說明需要繪製填充的距離更短了,最後將scrollingOffset
賦值給mLayoutState.mScrollingOffset
我們再回到scrollBy()
方法,在調用完updateLayoutState()
方法後會調用fill()
方法,源碼如下
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
這邊會判斷layoutState.mAvailable
是否小於0,如果是,就設置回原來的值,也就是在減去mScrollingOffset
之前的值,這裏是什麼目的還不清楚,難道是因爲layoutState.mAvailable
不可以爲負數?接下來會調用recycleByLayoutState()
方法,將在接下來被滑出屏幕的item回收,就是將item view全部remove,並回收到cache或者pool中。
然後調用while循環添加item,循環繼續的條件是item還沒有加載完或者RecyclerView可以無限加載且當前還沒有加載滿需要填充的空間。具體實施item加載的是layoutChunk()
方法,源碼如下:
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");
}
// 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;
}
LayoutParams params = (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;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecoratedWithMargins(view, left, top, right, bottom);
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.hasFocusable();
}
首先會調用layoutState.next(recycler)
方法獲取到一個item view,這個next方法源碼如下:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
先會判斷mScrapLis
t是不是null
,這個對象只有在layoutForPredictiveAnimations
方法中會被賦值,而layoutForPredictiveAnimations
內部會判斷要不要直接返回,我們這裏現在是沒有PredictiveAnimation所以layoutForPredictiveAnimations
方法會直接返回,也就是mScrapList
值等於null
,這樣的話就是會通過recycler.getViewForPosition
方法獲取itemview,getViewForPosition
方法最後會調用到tryGetViewHolderForPositionByDeadline
方法,此方法的第三個參數是一個long型的,表示需要在多長時間內完成item的創建和bind工作,如果是FOREVER_NS
,表示沒有時間限制,否則,就必須在規定時間內完成,否則就有可能返回null
或者一個沒有bind過的itemview。這裏我們傳進去的是FOREVER_NS
,所以沒有時間限制。tryGetViewHolderForPositionByDeadline
方法內部會依次從mChangedScrap,mAttachedScrap,mCachedViews,mViewCacheExtension,RecycledViewPool
去獲取itemview,如果都沒有獲取到,則會調用adapter的createViewHolder
方法來創建,創建view的代碼如下
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
可以看到最開始會判斷deadlineNs
是不是等於FOREVER_NS
,如果不是就會先預測在規定時間內是不是可以完成創建,如果不能則直接返回null了,這裏的預測時間是根據前面創建item的平均時間來確定的。在createViewHolder
方法內部會調用Adapter子類實現的onCreateViewHolder
方法
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
然後接下來有如下代碼:
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
+ exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
因爲我們這裏沒有prelayout,所以會走elseif,其中會判斷viewholder是不是需要重新bind,如果是就調用tryBindViewHolderByDeadline
方法,需要bind的條件有三個,`.沒有bind過,2.需要更新,3.當前holder已經不可用
private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
holder.mOwnerRecyclerView = RecyclerView.this;
final int viewType = holder.getItemViewType();
long startBindNs = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
// abort - we have a deadline we can't meet
return false;
}
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
attachAccessibilityDelegateOnBind(holder);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}
方法內部也會先判斷能不能在要求的時間內完成bind,如果不能就返回false,我能這裏是FOREVER_NS
,所以會調用
mAdapter.bindViewHolder(holder,offsetPosition)
,在bindViewHolder()
內部會調用Adapter子類的實現的onBindViewHolder()
方法
public final void bindViewHolder(VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
TraceCompat.endSection();
}
再回到tryGetViewHolderForPositionByDeadline()
方法,最後該方法會給viewholder設置一個LayoutParams
,如果viewholder當前沒有LayoutParams
,就會調用LayoutManager的generateDefaultLayoutParams()
方法來生成一個默認的LayoutParams
,此方法需要子類實現。
再回到layoutChunk()
方法,再貼一下代碼
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");
}
// 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;
}
LayoutParams params = (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;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecoratedWithMargins(view, left, top, right, bottom);
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.hasFocusable();
}
此時我們已經從next()
方法獲取到了一個view,此view不等於null,然後按照我們現在的場景,會進到addView(view)
方法。此方法最終會調用到LayoutManager的addViewInt
方法,此方法源碼如下
private void addViewInt(View child, int index, boolean disappearing) {
final ViewHolder holder = getChildViewHolderInt(child);
if (disappearing || holder.isRemoved()) {
// these views will be hidden at the end of the layout pass.
mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
} else {
// This may look like unnecessary but may happen if layout manager supports
// predictive layouts and adapter removed then re-added the same item.
// In this case, added version will be visible in the post layout (because add is
// deferred) but RV will still bind it to the same View.
// So if a View re-appears in post layout pass, remove it from disappearing list.
mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (holder.wasReturnedFromScrap() || holder.isScrap()) {
if (holder.isScrap()) {
holder.unScrap();
} else {
holder.clearReturnedFromScrapFlag();
}
mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
if (DISPATCH_TEMP_DETACH) {
ViewCompat.dispatchFinishTemporaryDetach(child);
}
} else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
// ensure in correct position
int currentIndex = mChildHelper.indexOfChild(child);
if (index == -1) {
index = mChildHelper.getChildCount();
}
if (currentIndex == -1) {
throw new IllegalStateException("Added View has RecyclerView as parent but"
+ " view is not a real child. Unfiltered index:"
+ mRecyclerView.indexOfChild(child) + mRecyclerView.exceptionLabel());
}
if (currentIndex != index) {
mRecyclerView.mLayout.moveView(currentIndex, index);
}
} else {
mChildHelper.addView(child, index, false);
lp.mInsetsDirty = true;
if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
mSmoothScroller.onChildAttachedToWindow(child);
}
}
if (lp.mPendingInvalidate) {
if (DEBUG) {
Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder);
}
holder.itemView.invalidate();
lp.mPendingInvalidate = false;
}
}
做了如下事情:
- 判斷需要添加的view的viewholder是需要消失的或者被移除的,兩者滿足一個就將此viewholder添加到ViewInfoState的
mLayoutHolderMap
中,用於之後的動畫。 - 判斷viewholder是不是scrap,如果是則清除相關標誌,並將view重新attach到RecyclerView
- 如果當前view的parent是當前RecyclerView,也就是說沒有dettach,則將此view移動到指定的index,這個過程是dettach到attach的過程
- 如果不滿足2,3兩種情況,則這個view是新建的view,則將此view add到RecyclerView,如果此view的位置是
mSmoothScroller
滑動的目標位置,則將targetview設爲此view - 最後如果此view需要刷新,也就是有重新bind,則調用
invalidate()
layoutChunk()
方法在調用完addview()
方法後,會調用measureChildWithMargins()
方法,此方法會用標準測量方法測量被添加的view,並將RecyclerView的滑動方向和itemdecoration考慮進去,這裏不再分析源碼。接下來把此item view消耗掉的空間數據保存到result的mConsumed
變量中。接下來會根據之前的測量結果,決定view的擺放位置,也就是調用layout()
方法,此過程中會將itemdecoration佔用的空間也考慮進去。最後如果此view是被removed或者changed,則會忽略掉它所消耗的空間,因爲remove的item不會消耗空間,反倒會增加空間,change的item所佔空間不變,不會影響後續空間消耗的計算,這裏的空間指的是根據用戶滑動距離,RecyclerView需要填充的空間,也就是mLayoutState.mAvailable
。到這裏,·layoutChunk()·就結束了,接下來就又要回到fill()
方法。再貼一遍code
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
在調用完layoutChunk()
方法之後,fill()
繼續做了如下事情:
- 判斷此次填充動作是不是結束,也就是看
layoutChunkResult.mFinished
這個變量,如果結束則break,我們這裏假設它沒有結束 - 接下來會根據item的填充方向和item消耗的空間更新
layoutState.mOffset
- 然後更新
layoutState.mAvailable
,更新此變量的條件是:1.所添加的view不需要忽略,2.正在佈局scrap的view,3.沒有進行pre-layout. - 更新
layoutState.mScrollingOffset
,並調用recycleByLayoutState()
方法回收暫時會被移除屏幕的item - 返回已經消耗的空間,單位是px
到這裏fill()
方法就結束了,再回到scrollBy()
方法,判斷返回的consumer是否小於0,小於0說明已經沒有item可以再添加,直接返回0(其實這部分不明白,爲什麼consumer小於0就是沒有item需要再添加),否則就根據scroll的像素個數來移動child views的位置,實現滑動效果,然後將scroll的距離保存在mLayoutState.mLastScrollDelta
,最後返回scroll。
scroll結束後再回到scrollByInternal()
方法,後續的代碼如下:
if (y != 0) {
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
...
repositionShadowingViews();
...
}
if (!mItemDecorations.isEmpty()) {
invalidate();
}
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
TYPE_TOUCH)) {
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
if (ev != null) {
ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
}
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
} else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
}
considerReleasingGlowsOnScroll(x, y);
}
if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(consumedX, consumedY);
}
if (!awakenScrollBars()) {
invalidate();
}
return consumedX != 0 || consumedY != 0;
接下來會調用repositionShadowingViews
方法,此方法裏做的事情和itemChange的動畫有關,我們這裏先不涉及動畫。然後判斷itemdecoration是否有指定,如果有的話就重繪RecyclerView。後面就是嵌套滑動和邊緣陰影的動作,然後再調用各個監聽scroll的listener,不再深究,最後返回是否有滑動被消耗掉。到這裏scrollByInternal()
也結束了,終於又回到onTouchEvent
了,在onTouchEvent
會判斷滑動是否有被消耗,如果有的話,就會調用getParent().requestDisallowInterceptTouchEvent(true)
方法來阻止RecyclerView的父控件來攔截touch事件,說明RecyclerView需要繼續處理後續事件。case Move
後面的代碼就不講了,比較簡單,都是一些資源回收,狀態重置的動作。
最後就是調用onTouchEvent
方法中Case Up
的代碼,這裏主要是做了一個fling()
的動作也就是快速滑動,這部分代碼這裏不講了,等後面有機會再看,其實做的事情和case move差不多。
好了,滑動部分就到這裏,我也是在學習源碼,有些地方如果有錯誤還請各位讀者提出