接着上篇繼續分析佈局流程。
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;
}