深入分析RecyclerView源碼——佈局流程(下)

接着上篇繼續分析佈局流程。

onLayoutChildren

所取onLayoutChildren是LinearLayoutManager的方法,RecyclerView把具體的佈局交給了佈局管理器去做,精簡後的代碼主要做了三件事:

一、確定錨點(updateAnchorInfoForLayout),所謂錨點就是佈局的基準點,一般情況下layout子view是從上到下或者從下到上的,但是當子view有焦點時,比如子view是EditText輸入時獲得了焦點,那麼會以該子view爲錨點,分別向上和向下layout。還有一個問題是,如何得知應該從第幾個子view開始layout,例如滑動之後怎麼知道?事實上RecyclerView的滑動不會走onLayoutChildren方法(滑動不走measure和layout方法,直接調用fill修改佈局),所以此處就只是簡單沿用舊佈局的position。

二、完成佈局,調用fill函數完成佈局。

三、處理動畫(layoutForPredictiveAnimations),RecyclerView的dispatchLayoutStep1和dispatchLayoutStep3已經處理了一部分動畫(見上篇),但是還不夠。當子view被擠出屏幕時應該執行平移動畫而不是淡出動畫(淡出動畫是被從Adapter的數據中徹底刪除才用的),爲了區分這裏需要特殊處理。在佈局完成後,被擠出屏幕的view實際上已經不在RecyclerView的mChildren數組中了,但是layoutForPredictiveAnimations會把該子view重新加進數組,不過是以DisappearingView的形式暫時存在,當平移動畫結束立即會被刪除。

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
	//確保生成LayoutState
	ensureLayoutState();
	mLayoutState.mRecycle = false;
	// resolve layout direction
	//是否需要反向佈局
	resolveShouldLayoutReverse();

	//獲取焦點,可能有可能沒有
	final View focused = getFocusedChild();
	if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
			|| mPendingSavedState != null) {
		mAnchorInfo.reset();
		mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
		// calculate anchor position and coordinate
		//如果有mPendingSavedState,直接拿來用,如果子項view有焦點,以焦點爲準,都沒有則取頭或者尾
		updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
		mAnchorInfo.mValid = true;
	} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)	
		//焦點view被軟鍵盤彈出屏幕時調用,把焦點view拉回屏幕頂端或底端
					>= mOrientationHelper.getEndAfterPadding()
			|| mOrientationHelper.getDecoratedEnd(focused)
			<= mOrientationHelper.getStartAfterPadding())) {
		mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
	}

	//計算額外空間,當平滑滑動時,會默認多一頁的額外空間
	mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
			? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
	mReusableIntPair[0] = 0;
	mReusableIntPair[1] = 0;
	calculateExtraLayoutSpace(state, mReusableIntPair);
	int extraForStart = Math.max(0, mReusableIntPair[0])
			+ mOrientationHelper.getStartAfterPadding();
	int extraForEnd = Math.max(0, mReusableIntPair[1])
			+ mOrientationHelper.getEndPadding();
	
	int startOffset;
	int endOffset;
	final int firstLayoutDirection;
	if (mAnchorInfo.mLayoutFromEnd) {
		firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
				: LayoutState.ITEM_DIRECTION_HEAD;
	} else {
		firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
				: LayoutState.ITEM_DIRECTION_TAIL;
	}
	//空方法
	onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
	//把屏幕上的子項view全部加入mAttachedScrap或mCachedViews或......
	detachAndScrapAttachedViews(recycler);
	mLayoutState.mInfinite = resolveIsInfinite();
	mLayoutState.mIsPreLayout = state.isPreLayout();
	// noRecycleSpace not needed: recycling doesn't happen in below's fill
	// invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN
	mLayoutState.mNoRecycleSpace = 0;
	if (mAnchorInfo.mLayoutFromEnd) {
		......
	} else {
		// fill towards end
		//把mAnchorInfo的數據讀取到mLayoutState
		updateLayoutStateToFillEnd(mAnchorInfo);
		mLayoutState.mExtraFillSpace = extraForEnd;
		fill(recycler, mLayoutState, state, false);
		endOffset = mLayoutState.mOffset;
		final int lastElement = mLayoutState.mCurrentPosition;
		if (mLayoutState.mAvailable > 0) {
			extraForStart += mLayoutState.mAvailable;
		}
		// fill towards start
		updateLayoutStateToFillStart(mAnchorInfo);
		mLayoutState.mExtraFillSpace = extraForStart;
		mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
		fill(recycler, mLayoutState, state, false);
		startOffset = mLayoutState.mOffset;

		if (mLayoutState.mAvailable > 0) {
			extraForEnd = mLayoutState.mAvailable;
			// start could not consume all it should. add more items towards end
			updateLayoutStateToFillEnd(lastElement, endOffset);
			mLayoutState.mExtraFillSpace = extraForEnd;
			fill(recycler, mLayoutState, state, false);
			endOffset = mLayoutState.mOffset;
		}
	}

	//對動畫的處理,例如增加一個view導致一個view被擠出屏幕,在此處把該view用addDisappearingView
	//再加回RecyclerView的mChildren和ChildrenHelper的mHiddenViews,並且執行平移動畫,動畫結束後會刪除該view
	layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
	if (!state.isPreLayout()) {
		mOrientationHelper.onLayoutComplete();
	} else {
		mAnchorInfo.reset();
	}
	mLastStackFromEnd = mStackFromEnd;
}

fill

fill函數沒有太多的內容,只是在空間有剩餘時循環調用layoutChunk,還有就是在PreLayout階段對於有變化的子view,fill函數忽略它們佔的空間,這是預佈局階段可以超出屏幕布局的原因。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
		RecyclerView.State state, boolean stopOnFocusable) {
	final int start = layoutState.mAvailable;
	//mExtraFillSpace在layoutForPredictiveAnimations中才會設置
	int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
	LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
	while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
		layoutChunkResult.resetInternal();
		//調用layoutChunk排列一個子view
		layoutChunk(recycler, state, layoutState, layoutChunkResult);
		layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
		//是否應該忽略該子view的空間,在預佈局階段會忽略有變化的子view的空間佔用以排列更多地view到屏幕外
		if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
				|| !state.isPreLayout()) {
			layoutState.mAvailable -= layoutChunkResult.mConsumed;
			remainingSpace -= layoutChunkResult.mConsumed;
		}

		if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
			layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
			if (layoutState.mAvailable < 0) {
				layoutState.mScrollingOffset += layoutState.mAvailable;
			}
            //專爲滑動事件設計,子view被滑出屏幕,在此處被回收
			recycleByLayoutState(recycler, layoutState);
		}
		if (stopOnFocusable && layoutChunkResult.mFocusable) {
			break;
		}
	}
	return start - layoutState.mAvailable;
}

layoutChunk

layoutChunk的重點一是layoutState.next獲得下一個view,二是對於addView和addDisappearingView的不同處理。addaddDisappearingView是對被擠出屏幕的view的特殊處理,其他情況都是走addView。被擠出屏幕並不是沒有了,所以不應該執行默認的淡出動畫,而應該執行平移動畫,addaddDisappearingView會把該view再次加到RecyclerView的mChildren數組,並設置FLAG_DISAPPEARED標誌位。動畫執行完畢後從mChildren數組中移除。

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
		LayoutState layoutState, LayoutChunkResult result) {
	//從各級緩存獲取下一個子view
	View view = layoutState.next(recycler);
	if (view == null) {
		result.mFinished = true;
		return;
	}
	RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
	//mScrapList一般情況下都是null,除非是在layoutForPredictiveAnimations中,
	//此時的mScrapList就是mAttachedScrap,也就是被擠出屏幕的view
	if (layoutState.mScrapList == null) {
		//是null說明在屏幕內,addView即可
		if (mShouldReverseLayout == (layoutState.mLayoutDirection
				== LayoutState.LAYOUT_START)) {
			addView(view);
		} else {
			addView(view, 0);
		}
	} else {
		//不是null說明是對被擠出屏幕的view做動畫處理,addDisappearingView是把該view,
		//暫時加回RecyclerView的mChildren數組,在平移動畫結束時再刪除,所以在動畫期間,
		//屏幕上的view和Adapter中的數據可能是不一致的,這就需要ChildHelper處理,
		//實際上RecyclerView處理子view的都是通過ChildHelper進行,ChildHelper有兩套方法,
		//一套常用的不考慮addDisappearingView的view,一套特殊的考慮addDisappearingView的view
		if (mShouldReverseLayout == (layoutState.mLayoutDirection
				== LayoutState.LAYOUT_START)) {
			addDisappearingView(view);
		} else {
			addDisappearingView(view, 0);
		}
	}
	measureChildWithMargins(view, 0, 0);
	result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
	......
	layoutDecoratedWithMargins(view, left, top, right, bottom);
	// Consume the available space if the view is not removed OR changed
	if (params.isItemRemoved() || params.isItemChanged()) {
		result.mIgnoreConsumed = true;
	}
	result.mFocusable = view.hasFocusable();
}

layoutForPredictiveAnimations

最後是上面多次提到的layoutForPredictiveAnimations,處理被擠出屏幕的view。

private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler,
            RecyclerView.State state, int startOffset, int endOffset) {
	if (!state.willRunPredictiveAnimations() ||  getChildCount() == 0 || state.isPreLayout()
			|| !supportsPredictiveItemAnimations()) {
		return;
	}
	int scrapExtraStart = 0, scrapExtraEnd = 0;
	//scrapList其實就是不可修改的mAttachedScrap
	final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
	final int scrapSize = scrapList.size();
	final int firstChildPos = getPosition(getChildAt(0));
	for (int i = 0; i < scrapSize; i++) {
		RecyclerView.ViewHolder scrap = scrapList.get(i);
		if (scrap.isRemoved()) {
			continue;
		}
		final int position = scrap.getLayoutPosition();
		final int direction = position < firstChildPos != mShouldReverseLayout
				? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
		if (direction == LayoutState.LAYOUT_START) {
			scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
		} else {
			scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
		}
	}

	mLayoutState.mScrapList = scrapList;
	if (scrapExtraStart > 0) {
		......
	}

	if (scrapExtraEnd > 0) {
		View anchor = getChildClosestToEnd();
		updateLayoutStateToFillEnd(getPosition(anchor), endOffset);
		mLayoutState.mExtraFillSpace = scrapExtraEnd;
		mLayoutState.mAvailable = 0;
		mLayoutState.assignPositionFromScrapList();
		//再走一遍fill,和onLayoutChildren不同的是,此處添加的都是DisappearingView
		fill(recycler, mLayoutState, state, false);
	}
	mLayoutState.mScrapList = null;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章