RecyclerView原始碼解析

檸檬茶就是力量發表於2019-07-23

作為一個很重要的view,recyclerview內部實現也是很複雜的,下面簡單概括以及總結一下一些比較重要的方法以及實現的思路

image.png

關鍵性幾個方法以及變數

  • dispatchLayoutStep1
  • dispatchLayoutStep2
  • dispatchLayoutStep3
  • ViewInfoStore
  • ViewInfoStore.ProcessCallback

dispatchLayoutStep1

第一步負責把舊的viewholder的資訊記錄下來,包括position,top,left等位置的資訊,封裝成ItemHolderInfo 作為一個成員放置到ViewInfoStore.InfoRecord中的preInfo變數然後放到ViewInfoStore中, 具體可以檢視 addToAppearedInPreLayoutHolders/addToPreLayout方法

dispatchLayoutStep2

第二步負責view的佈局,這一步由layoutmanager來處理,比如設定的LinearlayoutManager

dispatchLayoutStep3

第三步負責動畫的顯示,先是把佈局過後的view的資訊記錄下來,呼叫了addToPostLayout方法,封裝成ItemHolderInfo 作為一個成員放置到ViewInfoStore.InfoRecord中然後放到ViewInfoStore中,這裡的是記錄在了InfoRecordpostInfo中去,最後呼叫this.mViewInfoStore.process(this.mViewInfoProcessCallback); 去開始對view進行動畫操作

這裡step1和step3中分別用一個ItemHolderInfo記錄了佈局前後view的資訊,然後放在了InfoRecord中的preInfo以及postInfo中,這樣一個InfoRecord就可以記錄一個holder在佈局改變前後的位置資訊,方便後續做動畫上的變化

OnMeasure

這裡的測量分兩種方式,recyclerview有預設的測量策略,layoutmanager可以通過關閉autoMeasure來接管測量的邏輯,但是一般的都是使用預設的測量,

 int widthMode = MeasureSpec.getMode(widthSpec);
                int heightMode = MeasureSpec.getMode(heightSpec);
                this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
                boolean measureSpecModeIsExactly = widthMode == 1073741824 && heightMode == 1073741824;
                // 表明如果recyclerview有一個確切的寬高,就直接結束流程,否則就要開始下面的邏輯
                if (measureSpecModeIsExactly || this.mAdapter == null) {
                    return;
                }
                // 如果recyclerview是wrapcontent,寬高無法確定,就需要先去排列view,計算出高度再去設定
                // 這裡的state記錄了當前layout進行到了第幾步,詳細可以看下面的介紹
                if (this.mState.mLayoutStep == 1) {
                    this.dispatchLayoutStep1();
                }

                this.mLayout.setMeasureSpecs(widthSpec, heightSpec);
                this.mState.mIsMeasuring = true;
                this.dispatchLayoutStep2();
                this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
                if (this.mLayout.shouldMeasureTwice()) {
                    this.mLayout.setMeasureSpecs(MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), 1073741824), MeasureSpec.makeMeasureSpec(this.getMeasuredHeight(), 1073741824));
                    this.mState.mIsMeasuring = true;
                    this.dispatchLayoutStep2();
                    this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
                }
複製程式碼
RecyclerView.State 這個類封裝了當前RecyclerView的有用資訊。State的一個變數mLayoutStep表示了RecyclerView當前的佈局狀態,包括STEP_STARTSTEP_LAYOUTSTEP_ANIMATIONS三個,而RecyclerView的佈局過程也分為三步,其中,STEP_START表示即將開始佈局,需要呼叫dispatchLayoutStep1來執行第一步佈局,接下來,佈局狀態變為STEP_LAYOUT,表示接下來需要呼叫dispatchLayoutStep2裡進行第二步佈局,同理,第二步佈局後狀態變為STEP_ANIMATIONS,需要執行第三步佈局dispatchLayoutStep3。所以OnMeasure如果已經進行了第1,2步,後續dispatchLayout方法裡只會執行第三步,避免重複測量

Adapter

  • AdapterDataObserver
  • AdapterDataObservable

adapter中的資料重新整理是通過觀察者模式來觸發的,adapter內有一個AdapterDataObservable物件,當我們呼叫adapter的noti類方法通知recyclerview重新整理的時候,AdapterDataObservable會通知觀察者也就是AdapterDataObserver ,而AdapterDataObserver物件在recyclerview 裡有一個例項化物件RecyclerViewDataObserver可以檢視內部程式碼,這裡的程式碼就是最後去重新整理的邏輯

1.呼叫Adapterhelper中的具體方法去判斷是否執行重新整理操作,方法內會記錄當前操作型別是add/move/change還有itemvcount等資訊,一般返回true,如果是notifyDataSetChanged就直接執行到requestLayout 跳到第7
2.triggerUpdateProcessor
3. 執行mUpdateChildViewsRunnable這個runnable
4. consumePendingUpdateOperations
5. AdapterHelperpreProcess去判斷要add還是remove等操作
6. 呼叫AdapterHelperpreProcess去回撥到recyclerview中的對應的方法,然後呼叫offsetPositionsForAdd等方法,去確認變換過後的viewhodler的位置資訊,比如add remove 之後其他item的座標等,同時把cachedview也做同樣的操作
7. 呼叫dispathchLayout方法,走一遍測繪流程,就是一開始說的三步,這個時候新舊viewholder的資訊被分別記錄起來,最後呼叫this.mViewInfoStore.process(this.mViewInfoProcessCallback);
8. 在這裡面根據viewholder前後資訊,去判斷具體每個view是新增刪除還是移動等操作,回撥到recyclerview裡的mViewInfoProcessCallback也就是上面的callback,然後run一個runnable,讓recyclerview的ItemAnimator物件呼叫runPendingAnimations去開始對view進行動畫操作,具體可以檢視DefaultItemAnimator裡的實現

notifyDataSetChanged

notifyDataSetChanged會呼叫processDataSetCompletelyChanged,方法裡把mDataSetHasChangedAfterLayout設為了true,後續在step1裡processAdapterUpdatesAndSetAnimationFlags判斷把mRunSimpleAnimationsmRunPredictiveAnimations射程了false,所以就跳過了記錄舊holder這一步,所以呼叫的時候沒有完整的動畫

image.png

image.png

image.png

相關文章