<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:RecyclerView布局動畫原理了解嗎?

          共 7837字,需瀏覽 16分鐘

           ·

          2021-03-15 09:30

          前言

          「溫馨提示:文章有點長,建議關注微信公眾號“字節(jié)小站”收藏閱讀」

          本文主要通過以下幾個方面來講解RecyclerView的布局和動畫原理:

          1. 布局放置:RecyclerView#dispatchLayout()
          2. 預布局階段:RecyclerView#dispatchLayoutStep1()
          3. 布局階段:RecyclerView#dispatchLayoutStep2()
          4. 開啟動畫階段:RecyclerView#dispatchLayoutStep3()

          背景知識

          RecyclerView的Adapter有幾個notify相關的方法:

          • notifyDataSetChanged()
          • notifyItemChanged(int)
          • notifyItemInserted(int)
          • notifyItemRemoved(int)
          • notifyItemRangeChanged(int, int)
          • notifyItemRangeInserted(int, int)
          • notifyItemRangeRemoved(int, int)
          • notifyItemMoved(int, int)

          notifyDataSetChanged()與其他方法的區(qū)別:

          1. 會導致整個列表刷新,其它幾個方法則不會;
          2. 不會觸發(fā)RecyclerView的動畫機制,其它幾個方法則會觸發(fā)各種不同類型的動畫。

          1. 布局放置

          1.1 核心方法

          RecyclerView#dispatchLayout()

          1.2 作用

          1. 將View放置到合適的位置
          2. 記錄布局階段View的信息
          3. 處理動畫

          RecyclerView的布局我們可以分成三個階段,也可以精細分成五個階段。

          1.2.1 三個階段

          1.2.1.1 預布局階段

          當需要做動畫時,預布局階段才會工作,否則沒有實際意義,它對應dispatchLayoutStep1方法。動畫有開始狀態(tài)和結束狀態(tài),預布局完成后的RecyclerView是動畫的開始狀態(tài)。

          1.2.1.2 布局階段

          無論是否需要做動畫,布局階段都會工作,它對應dispatchLayoutStep2方法。布局完成后的狀態(tài)是用戶最終看到的狀態(tài),也是動畫的結束狀態(tài)。

          1.2.1.3 布局后階段

          布局完成后,需要執(zhí)行動畫操作,它對應的是dispatchLayoutStep3方法。當動畫完成后,還會進行View回收操作。

          1.2.2 五個階段

          1.2.2.1 預布局前

          在dispatchLayoutStep1方法調(diào)用onLayoutChildren方法之前。它會保存當前RecyclerView上所有子View的信息到ViewInfoStore中,F(xiàn)LAG增加FLAG_PRE。表示View在預布局前就顯示在RecyclerView上。

          1.2.2.2 預布局中

          在dispatchLayoutStep1方法調(diào)用onLayoutChildren方法時。它會根據(jù)算法,重新布置RecyclerView的子View,該階段可能會添加新的子View。該階段能夠確定哪些View最終是不會展示給用戶看的,F(xiàn)LAG增加FLAG_DISAPPEARED(例如:removed的View)。

          1.2.2.3 預布局后

          在dispatchLayoutStep1方法調(diào)用onLayoutChildren方法之后,將預布局完成后的子View與預布局前的子View對比,將新增的View的FLAG增加FLAG_APPEAR(調(diào)用notifyItemRemoved后,新填充的View)。

          1.2.2.4 布局中

          在dispatchLayoutStep2方法調(diào)用onLayoutChildren方法時。該階段會把被擠出屏幕的View的FLAG增加FLAG_DISAPPEARED。

          1.2.2.5 布局后

          在dispatchLayoutStep3方法中。會將最終的子View的FLAG增加FLAG_POST。

          1.2.3 動畫類型

          1.2.3.1 PERSISTENT

          預布局前和布局后都存在的View所做的動畫,位置有可能發(fā)生變化了,也有可能沒有發(fā)生變化。

          1.2.3.2 REMOVED

          在布局前對用戶可見,布局后不可見,而且數(shù)據(jù)已經(jīng)從數(shù)據(jù)源中刪除掉了。

          1.2.3.3 ADDED

          新增數(shù)據(jù)到數(shù)據(jù)源中,并且在布局后對用戶可見。

          1.2.3.4 DISAPPEARING

          數(shù)據(jù)一直都存在于數(shù)據(jù)源中,但是布局后從可見變成不可見狀態(tài)(例如因為其它View插入操作,導致被擠出屏幕外了)。

          1.2.3.5 APPEARING

          數(shù)據(jù)一直都存在于數(shù)據(jù)源中,但是布局后從不可見變成可見狀態(tài)(例如因為其它View被刪除,導致補位到屏幕內(nèi)了)。

          1.3 源碼解析

          1.3.1 RecyclerView#dispatchLayout()

          1. dispatchLayoutStep1()執(zhí)行預布局,記錄ViewHolder位置信息;
          2. dispatchLayoutStep2()執(zhí)行布局,用戶最終看到的效果;
          3. dispatchLayoutStep3()執(zhí)行動畫操作。

          2. 預布局階段

          2.1 核心方法

          1. RecyclerView#dispatchLayoutStep1()

          2. RecyclerView#processAdapterUpdatesAndSetAnimationFlags()

          3. LinearLayoutManager#onLayoutChildren()

          4. LinearLayoutManager#updateAnchorInfoForLayout()

          2.2 作用

          1. 處理Adapter變化
          2. 決定該執(zhí)行哪種類型動畫
          3. 保存當前RecyclerView上的子View的信息
          4. 如果需要執(zhí)行動畫,進行預布局

          2.3 源碼解析

          2.3.1 RecyclerView#dispatchLayoutStep1()

          1. 判斷是否需要開啟動畫功能
          2. 如果開啟動畫,將當前屏幕上的Item相關信息保存起來供后續(xù)動畫使用
          3. 如果開啟動畫,調(diào)用mLayout.onLayoutChildren方法預布局
          4. 預布局后,與第二步保存的信息對比,將新出現(xiàn)的Item信息保存到Appeared中

          2.3.2 RecyclerView#processAdapterUpdatesAndSetAnimationFlags()

          作用:判斷是否需要開啟動畫

          2.3.3 LinearLayoutManager#onLayoutChildren()

          以垂直方向的RecyclerView為例子,我們填充RecyclerView的方向有兩種,從上往下填充和從下往上填充。開始填充的位置不是固定的,可以從RecyclerView的任意位置處開始填充。

          1. 尋找填充的錨點(最終調(diào)用findReferenceChild方法);
          2. 移除屏幕上的Views(最終調(diào)用detachAndScrapAttachedViews方法);
          3. 從錨點處從上往下填充(調(diào)用fill和layoutChunk方法);
          4. 從錨點處從下往上填充(調(diào)用fill和layoutChunk方法);
          5. 如果還有多余的空間,繼續(xù)填充(調(diào)用fill和layoutChunk方法);
          6. 布局完成后有可能產(chǎn)生GAP,需要修復GAP;
          7. dispatchLayoutStep2階段調(diào)用layoutForPredictiveAnimation將scrapList中多余的ViewHolder填充(調(diào)用fill和layoutChunk方法)。

          2.3.3.1 尋找填充的錨點

          1. 優(yōu)先返回全部在屏幕內(nèi),未標記removed的View;
          2. 次優(yōu)先級返回不可見的View;
          3. 最低優(yōu)先級返回刪掉的view。

          2.3.3.2 移除屏幕上的Views

          1. 調(diào)用notifyItemChanged(position),position對應的ViewHolder會放入到mChangedScrap緩存中;
          2. 否則會放入到mAttachedScrap緩存中

          2.3.3.3 ~ 2.3.3.5 填充

          調(diào)用LinearLayoutManager#fill()和LinearLayoutManager#layoutChunk()

          1. 從緩存中獲取View或者創(chuàng)建View
          2. 如果是step1預布局階段,調(diào)用addView(),將標記為removed的view放入到DISAPPEARED動畫列表中
          3. 如果是step2布局階段,調(diào)用addDisappearingView(),將被擠出屏幕的view放入到DISAPPEARED動畫列表中
          4. 如果是removed的或者changed,不會記錄消耗的填充量

          2.3.3.6 修復GAP

          通過mOrientationHelper.offsetChildren(gap)直接填補GAP


          2.3.3.7 layoutForPredictiveAnimation

          為了做動畫,增加額外的Item

          1. 不需要做動畫,或者是預布局直接返回
          2. 從mAttachedScrap中遍歷到非removed的ViewHolder,但是返回的結果可能包含removed ViewHolder
          3. 如果遍歷找到了非Removed ViewHolder,填充View

          3. 布局階段

          3.1 核心方法

          1. RecyclerView#dispatchLayoutStep2()
          2. LinearLayoutManager#layoutChunk()
          3. LinearLayoutManager#addDisappearingView()
          4. ViewInfoStore#addToDisappearedInLayout()

          3.2 作用

          1. 根據(jù)數(shù)據(jù)源中的數(shù)據(jù)進行布局,真正展示給用戶看的最終界面
          2. 如果開啟動畫,將被擠出屏幕的View的保存到消失動畫列表中

          3.3 源碼解析

          3.3.1 RecyclerView#dispatchLayoutStep2()

          1. 將預布局模式改為false
          2. 布局填充View

          3.3.2 LinearLayoutManager#layoutChunk()

          布局階段將被擠出屏幕的View放入到DISAPPEARED動畫列表中

          3.3.3 LinearLayoutManager#addDisappearingView()

          把Removed的View或被擠出屏幕的View添加到Disappearing動畫列表

          3.3.4 ViewInfoStore#addToDisappearedInLayout()

          加入到Disappeared動畫列表

          4. 觸發(fā)動畫階段

          4.1 核心方法

          1. RecyclerView#dispatchLayoutStep3()
          2. ViewInfoStore#addToPostLayout()
          3. ViewInfoStore#process()
          4. ItemAnimator#animateAppearance()

          4.2 作用

          1. 清理工作
          2. 保存布局后的view的信息
          3. 觸發(fā)動畫
          4. 動畫執(zhí)行完回收工作

          4.3 源碼解析

          4.3.1 RecyclerView#dispatchLayoutStep3()

          1. 將當前屏幕上的View信息記錄到postLayout動畫列表中
          2. 執(zhí)行動畫
          3. 清理操作
          4. 布局完成回調(diào)

          4.3.2 ViewInfoStore#addToPostLayout()

          View信息記錄到postLayout動畫列表中

          4.3.3 ViewInfoStore#process()

          作用:執(zhí)行動畫

          工作流程,按優(yōu)先級執(zhí)行

          1. 調(diào)用unuse() 將view回收掉
          2. 執(zhí)行消失動畫
          • 2.1 預布局中不可見調(diào)用unuse()
          • 2.2 調(diào)用processDisappeared()
          1. 調(diào)用processPersistent()執(zhí)行move或者change動畫
          2. 執(zhí)行remove動畫
          3. 執(zhí)行insert動畫

          4.3.4 ViewInfoStore$InfoRecord

          作用:定義動畫類型

          • FLAG_DISAPPEARED:消失動畫,包含move和remove動畫
          • FLAG_APPEAR:出現(xiàn)動畫,包含move和insert動畫
          • FLAG_PRE:預布局前已經(jīng)顯示在RecyclerView上
          • FLAG_POST:布局后顯示在RecyclerView上
          • FLAG_APPEAR_AND_DISAPPEAR:先做出現(xiàn)動畫,再做消失動畫,無意義
          • FLAG_PRE_AND_POST:預布局前和布局后一直顯示在RecyclerView上
          • FLAG_APPEAR_PRE_AND_POST:在FLAG_PRE_AND_POST基礎上做出現(xiàn)動畫

          4.3.5 ViewInfoStore$ProccessCallback

          作用:定義四種處理動畫的接口

          • processDisappeared 處理消失動畫
          • processAppeared 處理出現(xiàn)動畫
          • processPersistent 處理一直存在動畫,包含move和change動畫
          • unused 不需要處理動畫,執(zhí)行回收

          4.3.6 接口實現(xiàn)

          4.3.7 ProccessCallback#processAppeared

          兵分兩路

          1. 調(diào)用ItemAnimator#animateAppearance()
          2. 調(diào)用RecyclerView#postAnimationRunner()

          4.3.8 一路兵:ItemAnimator#animateAppearance()

          4.3.8.1 SimpleItemAnimator#animateAppearance
          1. 該方法返回true表示需要做動畫
          2. 否則不需要做動畫
          3. 如果預布局前View已經(jīng)存在而且位置發(fā)生改變,處理MOVE動畫
          4. 否則,處理ADD動畫
          4.3.8.2 DefaultItemAnimator.animateMove
          1. 該方法并沒有真正執(zhí)行動畫
          2. 將MoveInfo保存到mPendingMoves中,以便RecyclerView#postAnimationRunner()使用
          3. 判斷是否有必要執(zhí)行MOVE動畫
          4. 回到preLayout的位置
          4.3.8.3 DefaultItemAnimator.animateAdd

          先調(diào)用setAlpha(0),以便做淡入動畫


          4.3.9 二路兵:RecyclerView#postAnimationRunner()

          4.3.9.1 RecyclerView#postAnimationRunner

          最終調(diào)用到ItemAnimator.runPendingAnimations

          4.3.9.2 DefaultItemAnimator.runPendingAnimations
          1. 首先執(zhí)行Remove動畫
          2. 然后同時執(zhí)行Move和Change動畫
          3. 最后執(zhí)行Add動畫

          動畫的總時長為removeDuration + Math.max(moveDuration, changeDuration) + addDuration

          4.3.10 RecyclerView$ItemAnimatorRestoreListener

          作用:動畫結束后執(zhí)行回收操作

          1. 動畫執(zhí)行完畢,removeAnimatingView
          2. 調(diào)用Recycler.recycleViewHolderInternal執(zhí)行回收操作

          5. 場景篇

          5.1 notifyItemRemoved場景

          5.1.1 場景描述

          1. 調(diào)用notifyItemRemoved()
          2. Adapter數(shù)據(jù)有100條,屏幕上有Item1~Item6 6個View,刪除Item1和Item2

          5.1.2 布局過程

          1. 將Item1 Item2對應的ViewHolder設置為REMOVE狀態(tài)
          2. 將所有的Item對應的ViewHolder的mPreLayoutPosition字段賦值為當前的position

          5.1.2.1 dispatchLayoutStep1階段

          1. 尋找填充的錨點,尋找錨點的邏輯是,從上往下,找到第一個非remove狀態(tài)的Item。在本Case中,找到Item3

          2. 移除屏幕上的Views,將它們的ViewHolder放入到Recycler的mAttachedScrap緩存中,這個緩存的好處是如果position對應上了,無需重新綁定,直接拿來用。

          3. 從錨點Item3處往下填充,mAttachedScrap只剩下ViewHolder2和ViewHolder1

          4. 從錨點Item3處往上填充Item2 Item1,因為Item2,Imte1已經(jīng)被remove掉了,它消耗的空間不會被記錄,那么到步驟5的時候還可以填充

          5. 還有多余的空間,繼續(xù)填充,把Item7、Item8填充到屏幕中

          6. 因為當前是預布局,直接返回


          5.1.2.2 dispatchLayoutStep2階段

          1. 尋找填充的錨點,尋找錨點的邏輯是,從上往下,找到第一個非remove狀態(tài)的Item,找到Item3

          2. 移除屏幕上的Views,將它們的ViewHolder放入到Recycler的mAttachedScrap緩存中

          3. 從錨點Item3處往下填充,填充到Item6為止,就沒有足夠的距離了,mAttachedScrap只剩下ViewHolder8,ViewHolder7,ViewHolder2,ViewHolder1

          4. 往上填充,雖然此時還有兩個View的高度,但是此時,上邊沒有數(shù)據(jù)了,此處不填充

          5. 此時還有兩個View的高度,繼續(xù)往下填充

          6. 修復GAP

          1. 當前是布局階段,但是因為ViewHolder1和ViewHolder2都是被Remove掉的,所以跳過

          5.1.2.3 dispatchLayoutStep3階段

          1. Item1、Item2做消失動畫
          2. Item3、Item4~Item8做移動動畫
          3. 動畫結束后,Item1、Item2會被回收到mCachedViews緩存池中

          5.2 notifyItemInserted場景

          5.2.1 場景描述

          假設在Item1下面插入兩條數(shù)據(jù)AddItem1,AddItem2

          5.2.2 布局過程

          5.2.2.1 dispatchLayoutStep1階段

          1. 尋找錨點,找到Item1
          2. 移除屏幕上的Views,放入到mAttachedScrap中
          3. 錨點處從上往下填充
          4. 錨點處從下往上填充,由上圖可知,上面沒有空間了,不填充
          5. 判斷是否還有剩余的空間,如果有在末尾填充,下面沒空間了,不填充
          6. 因為當前是預布局階段,不填充

          5.2.2.2 dispatchLayoutStep2階段

          1. 尋找錨點,找到Item1
          2. 移除屏幕上的Views,放入到mAttachedScrap中
          3. 錨點處從上往下填充,此時將變化后的數(shù)據(jù)填充到屏幕上,addItem1和addItem2被填充到item1下面
          4. 錨點處從下往上填充,由圖可知,沒有空間不填充
          5. 判斷是否還有剩余的空間,由圖可知,沒有空間不填充
          6. 當前是layoutStep2階段,會將mAttachScrap的內(nèi)容,填充到屏幕末尾,ViewHolder5和ViewHolder6對應的ItemView被填充

          5.2.2.3 dispatchLayoutStep3階段

          1. Item2、Item3~Item6做移動動畫
          2. addItem1、addItem2做淡入動畫
          3. 動畫結束后Item5、Item6被回收到mCachedViews緩存池中

          5.3 場景總結

          5.3.1 notifyItemRemoved場景

          刪除場景

          5.3.2 notifyItemInserted場景

          增加場景
          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操妣视频 | 亚洲无码1000 | 国产精品成人久久久久久久 | 自拍偷拍成人在线 | 大香蕉这里是精品12 |