RecyclerView測量和布局分析

在RecyclerView從測量到布局的過程中必會經(jīng)過的三個流程:
dispatchLayoutStep1->dispatchLayoutStep2->dispatchLayoutStep3

dispatchLayoutStep1

保存當(dāng)前的子View的一些信息

mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();

保存當(dāng)前適配器的獲取焦點的item,如果該item被刪除,則轉(zhuǎn)移到被刪除的item的前一個
saveFocusInfo

processAdapterUpdatesAndSetAnimationFlags —— 當(dāng)適配器更新,決定想要執(zhí)行的動畫類型

  1. 如果你的LayoutManager支持predictive動畫(根據(jù)你的LayoutManagersupportsPredictiveItemAnimations方法返回判斷是否支持predictive動畫)验辞,則會調(diào)用AdapterHelperpreProcess方法來選擇動畫的類型诀豁,不過在這一步并不執(zhí)行動畫榆骚,只是預(yù)處理
  2. 賦值mState.mRunSimpleAnimationsmState.mRunPredictiveAnimations

根據(jù)上一步得到的mState.mRunSimpleAnimations,如果為true谬哀,則將RecyclerView中已存在的子View添加到ViewInfoStore中

if (mState.mRunSimpleAnimations) {
    int count = mChildHelper.getChildCount();
    for (int i = 0; i < count; ++i) {
          ...
          mViewInfoStore.addToPreLayout(holder, animationInfo);
    ...
}

根據(jù)上一步得到的mState.mRunPredictiveAnimations,如果為true绽族,則調(diào)用子類LayoutManager的onLayoutChildren方法姨涡,找到是否有需要消失的子View

mLayout.onLayoutChildren(mRecycler, mState);
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
    ...
    if (wasHidden) {
        recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
    } else {
        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
    }
    ...
}

具體可以查看下一期的RecyclerView的動畫

dispatchLayoutStep2

為最終狀態(tài)的子View進行真正的布局,主要依靠子類LayoutManager的onLayoutChildren方法项秉,以LinearLayoutManager為例:

LinearLayoutManager的onLayoutChildren方法

找到布局的錨點和方向 —— updateAnchorInfoForLayout

這個錨點代表著RecyclerView的子View從哪個位置根據(jù)得到的方向開始布局

  1. 如果存在待定的滾動位置或已保存的狀態(tài)绣溜,則從中更新錨點信息
updateAnchorFromPendingData(state, anchorInfo)
  1. 通過updateAnchorFromChildren從RecyclerView中已添加的子View中找到錨點View
// 1. 優(yōu)先考慮獲得焦點的子View
final View focused = getFocusedChild();
...
// 2. 其次考慮靠近起始點和結(jié)束點的有效的子View
View referenceChild = anchorInfo.mLayoutFromEnd
        ? findReferenceChildClosestToEnd(recycler, state)
        : findReferenceChildClosestToStart(recycler, state);
  1. 錨點位置根據(jù)mStackFromEnd選擇0還是count-1
anchorInfo.assignCoordinateFromPadding();
anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;

分離RecyclerView中的所有子View慷彤,并將它們添加到Scrap緩存或Cache緩存中 ——detachAndScrapAttachedViews

for (int i = childCount - 1; i >= 0; i--) {0
    final View v = getChildAt(i);
    scrapOrRecycleView(recycler, i, v);
}

根據(jù)第一步找錨點時得到的方向來填充

if (mAnchorInfo.mLayoutFromEnd) {
    // fill towards start
    updateLayoutStateToFillStart(mAnchorInfo);
    fill(recycler, mLayoutState, state, false);
    // fill towards end
    updateLayoutStateToFillEnd(mAnchorInfo);
    fill(recycler, mLayoutState, state, false);
}else{
    // fill towards end
    updateLayoutStateToFillEnd(mAnchorInfo);
    fill(recycler, mLayoutState, state, false);
    // fill towards start
    updateLayoutStateToFillStart(mAnchorInfo);
    fill(recycler, mLayoutState, state, false);
}

填充布局 —— fill

計算填充空間
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
while循環(huán)通過layoutChunk方法布局子View娄蔼,直到填充空間不足
// 1. layoutState.mInfinite 表示是否還有足夠的空間允許擺放View
// 2. remainingSpace > 0 表示剩余空間是否大于0
// 3. layoutState.hasMore(state) 表示當(dāng)前的mCurrentPosition是否大于0,且小于mItemCount
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)){
      layoutChunk(recycler, state, layoutState, layoutChunkResult);
      ...
      if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {
      layoutState.mAvailable -= layoutChunkResult.mConsumed;
      // 每次填充完子View計算所剩的填充空間(即每次減去填充的布局所消耗的空間)
      remainingSpace -= layoutChunkResult.mConsumed;
      }
      // 將超出邊界的子View回收到Cache緩存或RecyclerViewPool中
      recycleByLayoutState(recycler, layoutState);
}
添加并擺放子View —— layoutChunk
  1. 通過Recycler的getViewForPosition方法獲得當(dāng)前mCurrentPosition對應(yīng)的itemview
View view = layoutState.next(recycler);
  1. 根據(jù)layoutState的狀態(tài)和是否翻轉(zhuǎn)決定是順序添加子View還是倒序底哗,addView的過程中岁诉,會判斷如果這個holder是來自Scrap緩存,則會調(diào)用unScrap方法將此holder從Scrap緩存中移除
if (mShouldReverseLayout == (layoutState.mLayoutDirection
        == LayoutState.LAYOUT_START)) {
    addView(view);
} else {
    addView(view, 0);
}
  1. 測量子View并將子View所占的高度記錄到LayoutChunkResult中
measureChildWithMargins
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
  1. 擺放子View跋选,調(diào)用子View的layout方法
layoutDecoratedWithMargins(view, left, top, right, bottom);

dispatchLayoutStep3

根據(jù)dispatchLayoutStep1中得到的mState.mRunSimpleAnimations值涕癣,如果為true,則處理適配器改變的動畫

if (mState.mRunSimpleAnimations) {
      ...
      mViewInfoStore.addToPostLayout(holder, animationInfo);
...
      mViewInfoStore.process(mViewInfoProcessCallback);
}

主要通過ViewInfoStore的process處理動畫前标,具體可以查看下一期的RecyclerView的動畫

回收Scrap緩存中的holder坠韩,添加到Cache緩存或RecyclerViewPool中,具體可查看ReclerView的緩存分析

mLayout.removeAndRecycleScrapInt(mRecycler);

清除一些狀態(tài)

例如StateInfo的兩個和動畫有關(guān)的變量

mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;

清除ViewInfoStore和焦點信息

mViewInfoStore.clear();
resetFocusInfo();

清除Scrap緩存中的mChangedScrap列表

if (mRecycler.mChangedScrap != null) {
    mRecycler.mChangedScrap.clear();
}

RecyclerView的測量和布局分析

如果我們的RecyclerView在xml中的寬高如下設(shè)置:

android:layout_width="match_parent"
android:layout_height="match_parent"

測量布局的流程如下圖:


RecyclerView_測量布局1.png

如果我們的RecyclerView在xml中的寬高如下設(shè)置:

android:layout_width="wrap_content"
android:layout_height="match_parent"

只要寬高有一個為wrap_content炼列,測量布局的流程如下圖:

RecyclerView_測量和布局2.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末只搁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俭尖,更是在濱河造成了極大的恐慌氢惋,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稽犁,死亡現(xiàn)場離奇詭異焰望,居然都是意外死亡,警方通過查閱死者的電腦和手機已亥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進店門熊赖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虑椎,你說我怎么就攤上這事秫舌。” “怎么了绣檬?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵足陨,是天一觀的道長。 經(jīng)常有香客問我娇未,道長墨缘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮镊讼,結(jié)果婚禮上宽涌,老公的妹妹穿的比我還像新娘。我一直安慰自己蝶棋,他們只是感情好卸亮,可當(dāng)我...
    茶點故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著玩裙,像睡著了一般兼贸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吃溅,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天溶诞,我揣著相機與錄音,去河邊找鬼决侈。 笑死螺垢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赖歌。 我是一名探鬼主播枉圃,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼庐冯!你這毒婦竟也來了孽亲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤肄扎,失蹤者是張志新(化名)和其女友劉穎墨林,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犯祠,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡旭等,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了衡载。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搔耕。...
    茶點故事閱讀 38,712評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖痰娱,靈堂內(nèi)的尸體忽然破棺而出弃榨,到底是詐尸還是另有隱情,我是刑警寧澤梨睁,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布鲸睛,位于F島的核電站,受9級特大地震影響坡贺,放射性物質(zhì)發(fā)生泄漏官辈。R本人自食惡果不足惜箱舞,卻給世界環(huán)境...
    茶點故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拳亿。 院中可真熱鬧晴股,春花似錦、人聲如沸肺魁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹅经。三九已至寂呛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞬雹,已是汗流浹背昧谊。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工刽虹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酗捌,地道東北人。 一個月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓涌哲,卻偏偏與公主長得像胖缤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阀圾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,606評論 2 350