<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布局動(dòng)畫原理了解嗎?

          共 8042字,需瀏覽 17分鐘

           ·

          2021-02-26 03:28


          前言

          本文主要通過以下幾個(gè)方面來講解RecyclerView的布局和動(dòng)畫原理:

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

          背景知識(shí)

          RecyclerView的Adapter有幾個(gè)notify相關(guān)的方法:

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

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

          1. 會(huì)導(dǎo)致整個(gè)列表刷新,其它幾個(gè)方法則不會(huì);
          2. 不會(huì)觸發(fā)RecyclerView的動(dòng)畫機(jī)制,其它幾個(gè)方法則會(huì)觸發(fā)各種不同類型的動(dòng)畫。

          1. 布局放置

          1.1 核心方法

          RecyclerView#dispatchLayout()

          1.2 作用

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

          RecyclerView的布局我們可以分成三個(gè)階段,也可以精細(xì)分成五個(gè)階段。

          1.2.1 三個(gè)階段

          1.2.1.1 預(yù)布局階段

          當(dāng)需要做動(dòng)畫時(shí),預(yù)布局階段才會(huì)工作,否則沒有實(shí)際意義,它對(duì)應(yīng)dispatchLayoutStep1方法。動(dòng)畫有開始狀態(tài)和結(jié)束狀態(tài),預(yù)布局完成后的RecyclerView是動(dòng)畫的開始狀態(tài)。

          1.2.1.2 布局階段

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

          1.2.1.3 布局后階段

          布局完成后,需要執(zhí)行動(dòng)畫操作,它對(duì)應(yīng)的是dispatchLayoutStep3方法。當(dāng)動(dòng)畫完成后,還會(huì)進(jìn)行View回收操作。

          1.2.2 五個(gè)階段

          1.2.2.1 預(yù)布局前

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

          1.2.2.2 預(yù)布局中

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

          1.2.2.3 預(yù)布局后

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

          1.2.2.4 布局中

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

          1.2.2.5 布局后

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

          1.2.3 動(dòng)畫類型

          1.2.3.1 PERSISTENT

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

          1.2.3.2 REMOVED

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

          1.2.3.3 ADDED

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

          1.2.3.4 DISAPPEARING

          數(shù)據(jù)一直都存在于數(shù)據(jù)源中,但是布局后從可見變成不可見狀態(tài)(例如因?yàn)槠渌黇iew插入操作,導(dǎo)致被擠出屏幕外了)。

          1.2.3.5 APPEARING

          數(shù)據(jù)一直都存在于數(shù)據(jù)源中,但是布局后從不可見變成可見狀態(tài)(例如因?yàn)槠渌黇iew被刪除,導(dǎo)致補(bǔ)位到屏幕內(nèi)了)。

          1.3 源碼解析

          1.3.1 RecyclerView#dispatchLayout()

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

          2. 預(yù)布局階段

          2.1 核心方法

          1. RecyclerView#dispatchLayoutStep1()

          2. RecyclerView#processAdapterUpdatesAndSetAnimationFlags()

          3. LinearLayoutManager#onLayoutChildren()

          4. LinearLayoutManager#updateAnchorInfoForLayout()

          2.2 作用

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

          2.3 源碼解析

          2.3.1 RecyclerView#dispatchLayoutStep1()

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

          2.3.2 RecyclerView#processAdapterUpdatesAndSetAnimationFlags()

          作用:判斷是否需要開啟動(dòng)畫

          2.3.3 LinearLayoutManager#onLayoutChildren()

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

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

          2.3.3.1 尋找填充的錨點(diǎn)

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

          2.3.3.2 移除屏幕上的Views

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

          2.3.3.3 ~ 2.3.3.5 填充

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

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

          2.3.3.6 修復(fù)GAP

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


          2.3.3.7 layoutForPredictiveAnimation

          為了做動(dòng)畫,增加額外的Item

          1. 不需要做動(dòng)畫,或者是預(yù)布局直接返回
          2. 從mAttachedScrap中遍歷到非removed的ViewHolder,但是返回的結(jié)果可能包含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ù)進(jìn)行布局,真正展示給用戶看的最終界面
          2. 如果開啟動(dòng)畫,將被擠出屏幕的View的保存到消失動(dòng)畫列表中

          3.3 源碼解析

          3.3.1 RecyclerView#dispatchLayoutStep2()

          1. 將預(yù)布局模式改為false
          2. 布局填充View

          3.3.2 LinearLayoutManager#layoutChunk()

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

          3.3.3 LinearLayoutManager#addDisappearingView()

          把Removed的View或被擠出屏幕的View添加到Disappearing動(dòng)畫列表

          3.3.4 ViewInfoStore#addToDisappearedInLayout()

          加入到Disappeared動(dòng)畫列表

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

          4.1 核心方法

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

          4.2 作用

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

          4.3 源碼解析

          4.3.1 RecyclerView#dispatchLayoutStep3()

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

          4.3.2 ViewInfoStore#addToPostLayout()

          View信息記錄到postLayout動(dòng)畫列表中

          4.3.3 ViewInfoStore#process()

          作用:執(zhí)行動(dòng)畫

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

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

          4.3.4 ViewInfoStore$InfoRecord

          作用:定義動(dòng)畫類型

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

          4.3.5 ViewInfoStore$ProccessCallback

          作用:定義四種處理動(dòng)畫的接口

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

          4.3.6 接口實(shí)現(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表示需要做動(dòng)畫
          2. 否則不需要做動(dòng)畫
          3. 如果預(yù)布局前View已經(jīng)存在而且位置發(fā)生改變,處理MOVE動(dòng)畫
          4. 否則,處理ADD動(dòng)畫
          4.3.8.2 DefaultItemAnimator.animateMove
          1. 該方法并沒有真正執(zhí)行動(dòng)畫
          2. 將MoveInfo保存到mPendingMoves中,以便RecyclerView#postAnimationRunner()使用
          3. 判斷是否有必要執(zhí)行MOVE動(dòng)畫
          4. 回到preLayout的位置
          4.3.8.3 DefaultItemAnimator.animateAdd

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


          4.3.9 二路兵:RecyclerView#postAnimationRunner()

          4.3.9.1 RecyclerView#postAnimationRunner

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

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

          動(dòng)畫的總時(shí)長(zhǎng)為removeDuration + Math.max(moveDuration, changeDuration) + addDuration

          4.3.10 RecyclerView$ItemAnimatorRestoreListener

          作用:動(dòng)畫結(jié)束后執(zhí)行回收操作

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

          5. 場(chǎng)景篇

          5.1 notifyItemRemoved場(chǎng)景

          5.1.1 場(chǎng)景描述

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

          5.1.2 布局過程

          1. 將Item1 Item2對(duì)應(yīng)的ViewHolder設(shè)置為REMOVE狀態(tài)
          2. 將所有的Item對(duì)應(yīng)的ViewHolder的mPreLayoutPosition字段賦值為當(dāng)前的position

          5.1.2.1 dispatchLayoutStep1階段

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

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

          3. 從錨點(diǎn)Item3處往下填充,mAttachedScrap只剩下ViewHolder2和ViewHolder1

          4. 從錨點(diǎn)Item3處往上填充Item2 Item1,因?yàn)镮tem2,Imte1已經(jīng)被remove掉了,它消耗的空間不會(huì)被記錄,那么到步驟5的時(shí)候還可以填充

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

          6. 因?yàn)楫?dāng)前是預(yù)布局,直接返回


          5.1.2.2 dispatchLayoutStep2階段

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

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

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

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

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

          6. 修復(fù)GAP

          1. 當(dāng)前是布局階段,但是因?yàn)閂iewHolder1和ViewHolder2都是被Remove掉的,所以跳過

          5.1.2.3 dispatchLayoutStep3階段

          1. Item1、Item2做消失動(dòng)畫
          2. Item3、Item4~Item8做移動(dòng)動(dòng)畫
          3. 動(dòng)畫結(jié)束后,Item1、Item2會(huì)被回收到mCachedViews緩存池中

          5.2 notifyItemInserted場(chǎng)景

          5.2.1 場(chǎng)景描述

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

          5.2.2 布局過程

          5.2.2.1 dispatchLayoutStep1階段

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

          5.2.2.2 dispatchLayoutStep2階段

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

          5.2.2.3 dispatchLayoutStep3階段

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

          5.3 場(chǎng)景總結(jié)

          5.3.1 notifyItemRemoved場(chǎng)景

          刪除場(chǎng)景

          5.3.2 notifyItemInserted場(chǎng)景

          增加場(chǎng)景


          —————END—————


          我是南塵,只做比心的公眾號(hào),歡迎關(guān)注我。

          推薦閱讀:

          nanchen,是一個(gè)怎樣的公眾號(hào)?

          掃盲:策略模式,成事兒還需要策略

          掃盲:責(zé)任鏈模式,先有責(zé)任后有鏈


          歡迎關(guān)注南塵的公眾號(hào):nanchen
          做不完的開源,寫不完的矯情,只做比心的公眾號(hào),如果你喜歡,你可以選擇分享給大家。如果你有好的文章,歡迎投稿,讓我們一起來分享。
          ? ? ??? ??長(zhǎng)按上方二維碼關(guān)注? ? ? ? 做不完的開源,寫不完的矯情? ? ? ? 一起來看 nanchen 同學(xué)的成長(zhǎng)筆記


          瀏覽 67
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  精品无码一区二区三区四区五区 | 欧色一级黄色视频 | 国产绿奴在线 | 欧美性爱乱伦视频网 | 欧美激情成人网站 |