1. 程式人生 > >Android中的RecyclerView原始碼分析

Android中的RecyclerView原始碼分析

RecyclerView元件

  1. Adapter
    提供資料以及資料變更通知
  2. LayoutManager
    佈局管理,負責測繪、指定item位置、item回收、
  3. ItemAnimator
    Item變更動畫

關鍵實現

1、ViewHolder複用

三層快取
第一層:Recycler中的mCachedViews

第二層:ViewCacheExtension 由開發者實現的快取策略,可通過setViewCacheExtension設定到RecyclerView中

第三層:RecycledViewPool中的mScrap 可在多個RecyclerView共享的View快取池

Scrapped View(廢棄的View):一個View雖然仍附屬在它的父檢視RecyclerView上,但是已經被標記為已使用或已遷移。這樣的View就叫做Scrapped View。

獲取一個View的過程

//RecyclerView.Recycler
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        private ArrayList<ViewHolder> mChangedScrap = null;

        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
//所有獲取View呼叫的入口
public View getViewForPosition(int position) {
            //呼叫內部實現
            //引數 position 獲取一個View的位置
            //     false   不進行預檢操作  dryrun
            return
getViewForPosition(position, false); } View getViewForPosition(int position, boolean dryRun) { //引數檢查 ... //如果處於提前佈局狀態,嘗試從mChangedScrap列表尋找 if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrap = holder != null; } //如果沒找到,則從mAttachedScrap列表中尋找 holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); //如果沒找到,或者找到的ViewHolder和OffsetPosition不匹 //配,則重新計算OffsetPosition。並且根據stable ids在 //mAttachedScrap中尋找(如果mAdapter存在stableIds)。 if
(mAdapter.hasStableIds()) { holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrap = true; } } //如果還找不到,則從mViewCacheExtension中尋找, //前提是開發者為RecyclerView設定了ViewCacheExtension if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); .... .... //如果沒有設定ViewCacheExtension,肯定還是找不到的, //那麼就會從RecycledViewPool中尋找。 //因為RecycledViewPool是多個RecyclerView共享的,如果找到合適的ViewHolder,要先更新需要顯示的資訊。 holder = getRecycledViewPool().getRecycledView(type); ... ... //最後一層快取也找不到的話,只能新建了 holder = mAdapter.createViewHolder(RecyclerView.this, type); ... //填充資料 mAdapter.bindViewHolder(holder, offsetPosition); ... return holder.itemView; }

2、資料變更通知(觀察者模式)

觀察者AdapterDataObserver,具體實現為RecyclerViewDataObserver,當資料來源發生變更時,及時響應介面變化

public static abstract class AdapterDataObserver {
        public void onChanged() {
            // Do nothing
        }

        public void onItemRangeChanged(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            // fallback to onItemRangeChanged(positionStart, itemCount) if app
            // does not override this method.
            onItemRangeChanged(positionStart, itemCount);
        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeRemoved(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            // do nothing
        }
    }

被觀察者AdapterDataObservable,內部持有觀察者AdapterDataObserver集合

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

        public void notifyItemRangeChanged(int positionStart, int itemCount) {
            notifyItemRangeChanged(positionStart, itemCount, null);
        }

        public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
            // since onItemRangeChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
            }
        }

        public void notifyItemRangeInserted(int positionStart, int itemCount) {
            // since onItemRangeInserted() is implemented by the app, it could do anything,
            // including removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
            }
        }

        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
            // since onItemRangeRemoved() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
            }
        }

        public void notifyItemMoved(int fromPosition, int toPosition) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
            }
        }
    }

Adapter內部持有AdapterDataObservable物件。
當我們為RecyclerView設定Adapter時,會向Adapter註冊一個觀察者RecyclerViewDataObserver。

adapter.registerAdapterDataObserver(mObserver);

當資料變更時,呼叫notify**方法時,Adapter內部的被觀察者會遍歷通知已經註冊的觀察者的對應方法,這時介面就會響應變更。

3、多種佈局型別展示
基類LayoutManager
子類有LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager

測量入口:RecyclerView的onMeasure方法

//mAutoMeasure代表是否是由已經存在的LayoutManager去執行測繪工作。
//當使用自定義的LayoutManager時,需要設定此值為false
if (mLayout.mAutoMeasure) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            //如果RecyclerView的測量模式為EXACTLY(準確模式)
            //則不必依賴子檢視的尺寸來確定RecyclerView本身的高度
            //子檢視的測量工作會延遲到onLayout方法中
            final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                    && heightMode == MeasureSpec.EXACTLY;
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            if (skipMeasure || mAdapter == null) {
                return;
            }
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            //此方法會真正執行LayoutManager的測量佈局操作
            dispatchLayoutStep2();
            .....
            .....
 /**
     * The second layout step where we do the actual layout of the views for the final state.
     * This step might be run multiple times if necessary (e.g. measure).
     */
    private void dispatchLayoutStep2() {
    ...
    ...
    //mLayout即RecyclerView設定的LayoutManager
    mLayout.onLayoutChildren(mRecycler, mState);
    ...
    ...
   }
//LayoutManager的這個方法是一個空實現,具體佈局操作由子類決定
//當自定義LayoutManager時,這是必須要實現的一個方法
public void onLayoutChildren(Recycler recycler, State state) {
            Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
        }
//這裡以LinearLayoutManager為例,說明佈局過程
//通過註釋可以很好的理解測量和佈局演算法流程
@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        //  item position.
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        // 4) scroll to fulfill requirements like stack from bottom.
        ...
        ...
        //第一步,通過檢查子檢視和其他變數,找出錨點座標和item位置錨點
        //第二步,從底部向起始位置填充
        //第三步,從頂部向末尾位置填充
        //第四步,滾動要滿足要求的位置,例如當從底部填充時
        ...
        ...
        //當錨點計算完成後,就開始填充view
        //LayoutManager的三個子類都有這個填充方法,但是不同的
        //的佈局型別,此方法會有不同的實現
        fill(recycler, mLayoutState, state, false);

4、Item變更動畫

5、其他

notifyDataSetChanged發生了什麼

1、為頁面中存在的ViewHolder和Recycler中mCachesView中快取的ViewHolder新增一個標誌:ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN

2、檢查有沒有等待執行的更新操作(add/remove/update/move)。如果沒有的話,並且當前沒有處於佈局狀態,並且沒有設定禁止LayoutFrozen,呼叫requestLayout()方法重新佈局。

3、如果有等待執行的更新操作怎麼辦?
不允許重新整理

notifyItemInserted發生了什麼

1、RecyclerView把Item的插入、刪除、移動、變更抽象成了一個個命令UpdateOp,並在AdapterHelper中維護了一個命令池。當插入一條Item時,向更新的命令佇列中新增一個“ADD”命令。

mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));

2、然後會觸發更新處理器,來處理新的命令。

3、RecyclerView會啟動一個Runnable來處理等待執行的命令。

    /**
     * Note: this Runnable is only ever posted if:
     * 1) We've been through first layout
     * 2) We know we have a fixed size (mHasFixedSize)
     * 3) We're attached
     */
    private final Runnable mUpdateChildViewsRunnable = new Runnable() {
        public void run() {
            if (!mFirstLayoutComplete || isLayoutRequested()) {
                // a layout request will happen, we should not do layout here.
                return;
            }
            if (mLayoutFrozen) {
                mLayoutRequestEaten = true;
                return; //we'll process updates when ice age ends.
            }
            consumePendingUpdateOperations();
        }
    };

但是我們在單步除錯的時候,發現條件isLayoutRequested()為true,所以這個Runnable什麼都沒幹,直接返回了。
????

既然isLayoutRequested()為true,說明接下來會重新佈局,可能執行更新的地方放在了layout中。
我們在dispatchLayout()方法中的第二步dispatchLayoutStep1();發現了processAdapterUpdatesAndSetAnimationFlags()方法;
該方法主要做了兩件事:第一,根據操作型別和數量更新Adapter中Item的位置(例,插入一個新的Item,原列表中Item的Position要依次加一)。第二,設定RecyclerView的狀態mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations為true。