Android RecyclerView 解析之繪製流程篇

前言: 當前市場上有不少成熟的RecyclerView分析文章,但那始終是其餘人總結出來的,還得本身動手分析,才知道本身理解了有多少,固然這個也算是加深對RecyclerView對理解吧;android

官方簡介:A flexible view for providing a limited window into a large data set.
一種靈活的視圖,在有限的窗口,展現大量的數據集;

在開始以前,爲了加深理解,咱們須要帶着疑問進行閱讀;緩存

(1)RecyclerView是怎麼加載數據的?架構

(2)RecyclerView是怎麼將View繪製到頁面上的?app

(3)RecyclerView是怎麼複用item的?ide

1.整體結構

RecyclerView主體架構.png

由上圖可知,RecyclerView主要由這幾部分組成;那他們的關係是啥呢? 具體是如何關聯的呢?且聽完細細道來!oop

數據層面:首頁RecyclerView須要將數據和view綁定起來,是經過Adapter加載ViewHolder來實現綁定數據的;
佈局層面:RecyclerView的Item的佈局是經過LayoutManager來進行佈局的;
複用層面:LayoutManger從Recycler獲取item來進行復用;源碼分析

總結:

1,Adapter:將數據轉化爲RecyclerView能夠識別的數據;佈局

2,ViewHolder:將數據和item綁定起來;post

3,LayoutManager:經過計算將Item佈局到頁面中;測試

4,Recycler:複用機制,統一管理Item,用於複用;

5,ItemDecoration:繪製item的樣式;

2.具體流程:

2.1 RecyclerView 初始化流程

首先,先來看看RecyclerView 的初始化流程,先舉個簡單的例子;

//獲取RecyclerView 控件
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
//建立adapter
MyAdapter adapter = new MyAdapter(list);
//建立LayoutManager
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
//設置LayoutManager
recyclerView.setLayoutManager(linearLayoutManager);
//設置Adapter
recyclerView.setAdapter(adapter);

1,咱們先來看看RecyclerView 的構造方法作了啥?

public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //建立觀察者
        this.mObserver = new RecyclerView.RecyclerViewDataObserver();
        //建立回收器
        this.mRecycler = new RecyclerView.Recycler();
        //建立佈局信息保存類
        this.mViewInfoStore = new ViewInfoStore();
        this.mUpdateChildViewsRunnable = new Runnable() {
            public void run() {
                if (RecyclerView.this.mFirstLayoutComplete && !RecyclerView.this.isLayoutRequested()) {
                    if (!RecyclerView.this.mIsAttached) {
                        RecyclerView.this.requestLayout();
                    } else if (RecyclerView.this.mLayoutFrozen) {
                        RecyclerView.this.mLayoutWasDefered = true;
                    } else {
                        RecyclerView.this.consumePendingUpdateOperations();
                    }
                }
            }
        };
        ...
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
            this.mClipToPadding = a.getBoolean(0, true);
            a.recycle();
        } else {
            this.mClipToPadding = true;
        }
        ...
        this.mAccessibilityManager = (AccessibilityManager)this.getContext().getSystemService("accessibility");
        this.setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
        boolean nestedScrollingEnabled = true;
        if (attrs != null) {
            int defStyleRes = 0;
            TypedArray a = context.obtainStyledAttributes(attrs, styleable.RecyclerView, defStyle, defStyleRes);
            //從佈局文件獲取Layoutmanger的名稱
            String layoutManagerName = a.getString(styleable.RecyclerView_layoutManager);
            int descendantFocusability = a.getInt(styleable.RecyclerView_android_descendantFocusability, -1);
            if (descendantFocusability == -1) {
                this.setDescendantFocusability(262144);
            }

            this.mEnableFastScroller = a.getBoolean(styleable.RecyclerView_fastScrollEnabled, false);
            //經過layoutManger的名稱進行反射建立layoutManager,並設置給RecycleView
            this.createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
          ...
        } else {
            this.setDescendantFocusability(262144);
        }
        //設置是否支持嵌套滾動,默認爲true
        this.setNestedScrollingEnabled(nestedScrollingEnabled);
    }

從構造方法能夠看出,裏面作了一大堆初始化的操做,最主要看一下這個建立layoutManager的方法createLayoutManager();

根據佈局屬性進行反射來建立layoutManager;

private void createLayoutManager(Context context, String className, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        if (className != null) {
            className = className.trim();
            if (!className.isEmpty()) {
                className = this.getFullClassName(context, className);

                try {
                    ClassLoader classLoader;
                    if (this.isInEditMode()) {
                        classLoader = this.getClass().getClassLoader();
                    } else {
                        classLoader = context.getClassLoader();
                    }

                    Class<? extends RecyclerView.LayoutManager> layoutManagerClass = classLoader.loadClass(className).asSubclass(RecyclerView.LayoutManager.class);
                    Object[] constructorArgs = null;

                    Constructor constructor;
                    try {
                        //經過反射建立佈局構造器
                        constructor = layoutManagerClass.getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
                        constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
                    } catch (NoSuchMethodException var13) {
                        try {
                            constructor = layoutManagerClass.getConstructor();
                        } catch (NoSuchMethodException var12) {
                            var12.initCause(var13);
                            throw new IllegalStateException(attrs.getPositionDescription() + ": Error creating LayoutManager " + className, var12);
                        }
                    }

                    constructor.setAccessible(true);

                     //將建立出來的LayoutManger設置給RecycleView       
                     this.setLayoutManager((RecyclerView.LayoutManager)constructor.newInstance(constructorArgs));
                } catch (ClassNotFoundException var14) {
                    throw new IllegalStateException(attrs.getPositionDescription() + ": Unable to find LayoutManager " + className, var14);
                } catch (InvocationTargetException var15) {
                    throw new IllegalStateException(attrs.getPositionDescription() + ": Could not instantiate the LayoutManager: " + className, var15);
                } catch (InstantiationException var16) {
                    throw new IllegalStateException(attrs.getPositionDescription() + ": Could not instantiate the LayoutManager: " + className, var16);
                } catch (IllegalAccessException var17) {
                    throw new IllegalStateException(attrs.getPositionDescription() + ": Cannot access non-public constructor " + className, var17);
                } catch (ClassCastException var18) {
                    throw new IllegalStateException(attrs.getPositionDescription() + ": Class is not a LayoutManager " + className, var18);
                }
            }
        }

    }

再看一下setLayoutManager()這個方法裏面作了啥操做?

public void setLayoutManager(@Nullable RecyclerView.LayoutManager layout) {
        if (layout != this.mLayout) {
           //中止當前的滾動操做
            this.stopScroll();
            if (this.mLayout != null) {
                //判斷當前的layoutManager若是爲空,則將該layoutManager的狀態進行初始化;
                if (this.mItemAnimator != null) {
                    this.mItemAnimator.endAnimations();
                }

                this.mLayout.removeAndRecycleAllViews(this.mRecycler);
                this.mLayout.removeAndRecycleScrapInt(this.mRecycler);
                this.mRecycler.clear();
                if (this.mIsAttached) {
                    this.mLayout.dispatchDetachedFromWindow(this, this.mRecycler);
                }

                this.mLayout.setRecyclerView((RecyclerView)null);
                this.mLayout = null;
            } else {
                this.mRecycler.clear();
            }

            this.mChildHelper.removeAllViewsUnfiltered();
            //將當前的layoutManager賦值給成員變量
            this.mLayout = layout;
            if (layout != null) {
                if (layout.mRecyclerView != null) {
                    throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView:" + layout.mRecyclerView.exceptionLabel());
                }
                //將當前的RecyclerView賦值給layoutManager
                this.mLayout.setRecyclerView(this);
                if (this.mIsAttached) {
                    this.mLayout.dispatchAttachedToWindow(this);
                }
            }
            //更新一下RecyclerView的緩存
            this.mRecycler.updateViewCacheSize();
            //觸發從新佈局
            this.requestLayout();
        }
    }
總結:看完RecyclerView的構造方法,裏面主要是作了一些初始化的操做,並建立了layoutManager設置給RecyclerView(若是佈局屬性有設置的話);

2,看完了RecyclerView的setLayoutManager()的流程,咱們繼續接着分析,看一下setAdapter()具體作了啥?

public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
        this.setLayoutFrozen(false);
        //主要模塊
        this.setAdapterInternal(adapter, false, true);
        this.processDataSetCompletelyChanged(false);
        this.requestLayout();
    }

跟進源碼,咱們主要分析setAdapterInternal()這個方法,讓咱們看看這個源碼裏面作了什麼操做;

private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
        if (this.mAdapter != null) {
            //解註冊以前的數據觀察者
            this.mAdapter.unregisterAdapterDataObserver(this.mObserver);
            this.mAdapter.onDetachedFromRecyclerView(this);
        }

        if (!compatibleWithPrevious || removeAndRecycleViews) { 
            //進行初始化操做,初始化layoutManger,初始化mRecycler
            this.removeAndRecycleViews();
        }
        
        this.mAdapterHelper.reset();
        RecyclerView.Adapter oldAdapter = this.mAdapter;
        //將adapter賦值給當前成員變量
        this.mAdapter = adapter;
        if (adapter != null) {
            //adapter註冊數據觀察者,用於監聽數據的增刪改查
            adapter.registerAdapterDataObserver(this.mObserver);
            adapter.onAttachedToRecyclerView(this);
        }

        if (this.mLayout != null) {
            this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter);
        }

        this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, compatibleWithPrevious);
        this.mState.mStructureChanged = true;
    }

這個方法裏面主要是給adapter註冊數據監聽,用於數據的增刪改查的刷新,並作一些初始化的操做;

咱們再看一下這個觀察者裏面主要作了什麼操做,具體的實現是在RecyclerViewDataObserver 這個類裏面;

private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        public void onChanged() {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            RecyclerView.this.mState.mStructureChanged = true;
            RecyclerView.this.processDataSetCompletelyChanged(true);
            if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
                RecyclerView.this.requestLayout();
            }

        }

        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                this.triggerUpdateProcessor();
            }

        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            if (RecyclerView.this.mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
                this.triggerUpdateProcessor();
            }

        }

        public void onItemRangeRemoved(int positionStart, int itemCount) {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            if (RecyclerView.this.mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
                this.triggerUpdateProcessor();
            }

        }

        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            if (RecyclerView.this.mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
                this.triggerUpdateProcessor();
            }

        }

        void triggerUpdateProcessor() {
            if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
            } else {
                RecyclerView.this.mAdapterUpdateDuringMeasure = true;
                RecyclerView.this.requestLayout();
            }

        }
    }

看到了咱們很熟悉的方法,即adapter刷新數據所調用的方法;咱們主要分析其中一個方法便可,讓咱們來看一下onItemRangeChanged()這個方法;
這裏面主要分爲兩步:

public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            //這裏經過AdapterHelper將傳進來的信息保存起來
            if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                //從新佈局
                this.triggerUpdateProcessor();
            }

        }

(1)經過AdapterHelper將傳進來的信息保存起來;

boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        if (itemCount < 1) {
            return false;
        } else {
            this.mPendingUpdates.add(this.obtainUpdateOp(4, positionStart, itemCount, payload));
            this.mExistingUpdateTypes |= 4;
            return this.mPendingUpdates.size() == 1;
        }
    }

(2)經過triggerUpdateProcessor()方法觸發RecyclerView從新佈局;

void triggerUpdateProcessor() {
       if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
                //當前有動畫正在執行的時候會走這裏
                ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
            } else { 
                //觸發從新佈局
                RecyclerView.this.mAdapterUpdateDuringMeasure = true;
                RecyclerView.this.requestLayout();
            }

        }

3. 工做流程

3.1 主體關係

首先咱們來看一下各個模塊的關係;
關係圖.png
經過上圖大致能夠看出這幾個模塊的關係:

(1)RecyclerView經過LayoutManager來進行佈局操做;

(2)LayoutManager從Recycler裏面獲取複用的item來進行佈局;

(3)Recycler管理着ViewHolder的建立與複用;

(4)Adapter將數據和ViewHolder綁定起來,並和RecyclerView註冊觀察者;

(5)RecyclerView經過ItemDecoration進行item樣式的繪製;

接下來經過源碼來細細剖析,看看具體是怎麼實現的;
那麼咱們接着上面分析的setAdapter()方法繼續分析,在setAdapter()方法裏,最後調用來requestLayout(),來觸發RecyclerView 的繪製流程;
這個requestLayout()這個方法最終會調用到ViewRootImp裏面的requestLayout()方法;

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            //觸發繪製流程
            scheduleTraversals();
        }
    }

在ViewRootImp裏調用requestLayout()方法進行繪製,咱們主要看scheduleTraversals()方法,裏面最終會調用到performTraversals()方法,源碼以下;

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
...
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...
void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
...

performTraversals()這個方法裏面執行了三大步驟,測量(measure),佈局(layout),繪製(draw),完成的view的工做流程,將頁面繪製出來;

{
        // cache mView since it is used so much below...
        final View host = mView;

        ...
        if (!mStopped || mReportNextDraw) {
           
             //執行view的測量流程
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        } else {
            ...
        }
       ...
        if (didLayout) {
            //執行view的佈局流程
            performLayout(lp, mWidth, mHeight);

            ...
        }
        ...
        if (!cancelDraw && !newSurface) {
            ...
            //執行view的繪製流程
            performDraw();
        } else {
           ...
        }
    }

從上面整理的方法來看,繪製流程主要是這performMeasure(),performLayout(),performDraw();最終會觸發RecyclerView的onMeasure(),onLayout(),onDraw()方法,具體源碼這裏就不過多分析了,感興趣的能夠看一下View的繪製流程;

讓咱們一個個來進行分析,先看看RecyclerView的onMeasure()方法裏面作了什麼?

onMeasure()分析:

protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            //1.判斷當前的LayoutManger是否爲空,爲空則走RecyclerView默認測量的方法 ;
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
         //2.LayoutManger開啓自動測量時走這裏處理邏輯;
        if (mLayout.mAutoMeasure) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            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;
            dispatchLayoutStep2();

            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            // if RecyclerView has non-exact width and height and if there is at least one child
            // which also has non-exact width & height, we have to re-measure.
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
          //3.LayoutManger沒有開啓自動測量時走這裏處理邏輯;
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // custom onMeasure
            if (mAdapterUpdateDuringMeasure) {
                eatRequestLayout();
                onEnterLayoutOrScroll();
                processAdapterUpdatesAndSetAnimationFlags();
                onExitLayoutOrScroll();

                if (mState.mRunPredictiveAnimations) {
                    mState.mInPreLayout = true;
                } else {
                    // consume remaining updates to provide a consistent state with the layout pass.
                    mAdapterHelper.consumeUpdatesInOnePass();
                    mState.mInPreLayout = false;
                }
                mAdapterUpdateDuringMeasure = false;
                resumeRequestLayout(false);
            }

            if (mAdapter != null) {
                mState.mItemCount = mAdapter.getItemCount();
            } else {
                mState.mItemCount = 0;
            }
            eatRequestLayout();
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            resumeRequestLayout(false);
            mState.mInPreLayout = false; // clear
        }
    }
這裏面主要分三種狀況,而咱們大部分狀況都是走第三步,經過查看官方的LayoutManger的源碼得知,LinearLayoutManager和StaggeredGridLayoutManager都開啓了自動測試,而GridLayoutManager繼承自LinearLayoutManager;因此,官方的LayoutManager都開啓了自動測量,這裏咱們只須要關注第二步的邏輯;

從上面源碼能夠看出,RecyclerView經過LayoutManger裏的onMeasure()來進行測量操做;
經過State這個類來進行佈局和測試狀態的記錄,這裏的mLayoutStep 包括STEP_START、STEP_LAYOUT 、 STEP_ANIMATIONS三個狀態;

從源碼分析,此時測量完畢以後,判斷當前狀態爲開始的時候(STEP_START),調用了dispatchLayoutStep1()進行了一系列的操做,這個方法執行完了以後,會將mLayoutStep 賦值爲STEP_LAYOUT;後面就執行了dispatchLayoutStep2(),在這個方法裏將mLayoutStep 賦值爲STEP_ANIMATIONS;

這裏咱們能夠理解爲,RecyclerView在測量完畢以後,就開始進行佈局了,分別執行了dispatchLayoutStep1()和dispatchLayoutStep2()方法;到此onMeasure()分析完了;

讓咱們繼續接着往下看,此時RecyclerView的onMeasure()已經執行完了,接下來會執行onLayout()方法,讓咱們看看這個方法裏面作了啥?

onLayout()分析:

先看一下源碼

protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection("RV OnLayout");
        //執行佈局操做
        this.dispatchLayout();
        TraceCompat.endSection();
        this.mFirstLayoutComplete = true;
    }

主要看dispatchLayout()這個方法

void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }
經過上面源碼能夠看出,以前在onMeasure()裏的這個dispatchLayoutStep2()方法裏面已經把mLayoutStep 賦值爲STEP_ANIMATIONS,那麼這裏就會走最後一個方法dispatchLayoutStep3();若是沒有執行STEP_START方法,那麼就會依次執行dispatchLayoutStep1(),dispatchLayoutStep2(),dispatchLayoutStep3()這幾個佈局方法;讓咱們來一個個分析;
dispatchLayoutStep1():
private void dispatchLayoutStep1() {
        mState.assertLayoutStep(State.STEP_START);
        mState.mIsMeasuring = false;
        eatRequestLayout();
        mViewInfoStore.clear();
        onEnterLayoutOrScroll();
        processAdapterUpdatesAndSetAnimationFlags();
        saveFocusInfo();
        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        mState.mItemCount = mAdapter.getItemCount();
        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

        if (mState.mRunSimpleAnimations) {
            // Step 0: Find out where all non-removed items are, pre-layout
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                    continue;
                }
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPreLayoutInformation(mState, holder,
                                ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                holder.getUnmodifiedPayloads());
                mViewInfoStore.addToPreLayout(holder, animationInfo);
                if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                        && !holder.shouldIgnore() && !holder.isInvalid()) {
                    long key = getChangedHolderKey(holder);
                    // This is NOT the only place where a ViewHolder is added to old change holders
                    // list. There is another case where:
                    //    * A VH is currently hidden but not deleted
                    //    * The hidden item is changed in the adapter
                    //    * Layout manager decides to layout the item in the pre-Layout pass (step1)
                    // When this case is detected, RV will un-hide that view and add to the old
                    // change holders list.
                    mViewInfoStore.addToOldChangeHolders(key, holder);
                }
            }
        }
        if (mState.mRunPredictiveAnimations) {
            // Step 1: run prelayout: This will use the old positions of items. The layout manager
            // is expected to layout everything, even removed items (though not to add removed
            // items back to the container). This gives the pre-layout position of APPEARING views
            // which come into existence as part of the real layout.

            // Save old positions so that LayoutManager can run its mapping logic.
            saveOldPositions();
            final boolean didStructureChange = mState.mStructureChanged;
            mState.mStructureChanged = false;
            // temporarily disable flag because we are asking for previous layout
            mLayout.onLayoutChildren(mRecycler, mState);
            mState.mStructureChanged = didStructureChange;

            for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                final View child = mChildHelper.getChildAt(i);
                final ViewHolder viewHolder = getChildViewHolderInt(child);
                if (viewHolder.shouldIgnore()) {
                    continue;
                }
                if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                    int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                    boolean wasHidden = viewHolder
                            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    if (!wasHidden) {
                        flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    }
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                            mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                    if (wasHidden) {
                        recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                    } else {
                        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                    }
                }
            }
            // we don't process disappearing list because they may re-appear in post layout pass.
            clearOldPositions();
        } else {
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

這個方法主要作了ViewHolder信息的保存,裏面經過遍歷當前的子View,根據子view的位置信息建立ItemHolderInfo,並添加到 ViewInfoStore這個類裏面進行保存;
看一下ItemHolderInfo這個類;

public static class ItemHolderInfo {
            public int left;
            public int top;
            public int right;
            public int bottom;
            public ItemHolderInfo() {
            }
          ...
            public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder,
                    @AdapterChanges int flags) {
                final View view = holder.itemView;
                this.left = view.getLeft();
                this.top = view.getTop();
                this.right = view.getRight();
                this.bottom = view.getBottom();
                return this;
            }
        }

class ViewInfoStore {

    private static final boolean DEBUG = false;

    /**
     * View data records for pre-layout
     */
    @VisibleForTesting
    final ArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();

    @VisibleForTesting
    final LongSparseArray<RecyclerView.ViewHolder> mOldChangedHolders = new LongSparseArray<>();

    /**
     * Clears the state and all existing tracking data
     */
    void clear() {
        mLayoutHolderMap.clear();
        mOldChangedHolders.clear();
    }

    /**
     * Adds the item information to the prelayout tracking
     * @param holder The ViewHolder whose information is being saved
     * @param info The information to save
     */
    void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.preInfo = info;
        record.flags |= FLAG_PRE;
    }
}

經過源碼能夠看出,在dispatchLayoutStep1()方法裏會先遍歷子view,並建立ItemHolderInfo,而後再經過ViewInfoStore的addToPreLayout()的這個方法將ItemHolderInfo賦值給InfoRecord,再保存到mLayoutHolderMap這個集合裏面;

下面咱們再來分析一下dispatchLayoutStep2()這個方法裏面作來啥?

dispatchLayoutStep2():
private void dispatchLayoutStep2() {
       private void dispatchLayoutStep2() {
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
        mAdapterHelper.consumeUpdatesInOnePass();
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

        // Step 2: Run layout
        mState.mInPreLayout = false;
        //  開始真正的去佈局
        mLayout.onLayoutChildren(mRecycler, mState);

        mState.mStructureChanged = false;
        mPendingSavedState = null;

        // onLayoutChildren may have caused client code to disable item animations; re-check
        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
        mState.mLayoutStep = State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
    }
    }

經過上面的源碼能夠看出,dispatchLayoutStep2()裏面就開始真正的去佈局了,經過onLayoutChildre()方法進行佈局,具體的實現都在LayoutManager的子類裏面;咱們經常使用的LayoutManager基本上是LinearLayoutManager,那麼這裏咱們具體來分析一下這個類裏面是怎麼實現的;

先看一下源碼:

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
      
       ...
        final View focused = getFocusedChild();
        if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                || mPendingSavedState != null) {
            mAnchorInfo.reset();
            // 獲取佈局的錨點
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
        } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                        >= mOrientationHelper.getEndAfterPadding()
                || mOrientationHelper.getDecoratedEnd(focused)
                <= mOrientationHelper.getStartAfterPadding())) {
             ...
            // 更新錨點信息
            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
        }
        
        //判斷是不是從後往前開始佈局
        if (mAnchorInfo.mLayoutFromEnd) {
            ...
            //佈局操做
            fill(recycler, mLayoutState, state, false);
           ...
        } else {
             ...
            // fill towards end
            fill(recycler, mLayoutState, state, false);

          // fill towards start
          fill(recycler, mLayoutState, state, false);
          ...
        }

        ...
    }

這裏把代碼簡化了,咱們只須要關注幾個重點的方法;這裏的佈局操做是,經過尋找佈局的錨點(mAnchorInfo),判斷是從後往前佈局仍是從前日後佈局,而後調用fill()方法進行佈局;

尋找佈局的錨點是經過updateAnchorInfoForLayout(recycler, state, mAnchorInfo)這個方法

private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
            AnchorInfo anchorInfo) {
        ...

        if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
            if (DEBUG) {
                Log.d(TAG, "updated anchor info from existing children");
            }
            return;
        }
        ...
    }

這裏咱們只須要關注updateAnchorFromChildren這個方法,跟進去看一下具體作了什麼;

private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
            RecyclerView.State state, AnchorInfo anchorInfo) {
        if (getChildCount() == 0) {
            return false;
        }
        final View focused = getFocusedChild();
        if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
            anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
            return true;
        }
        if (mLastStackFromEnd != mStackFromEnd) {
            return false;
        }
        View referenceChild = anchorInfo.mLayoutFromEnd
                ? findReferenceChildClosestToEnd(recycler, state)
                : findReferenceChildClosestToStart(recycler, state);
        if (referenceChild != null) {
            anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
            ...
            }
            return true;
        }
        return false;
    }

從這裏的源碼能夠看出,先經過getFocusedChild()去獲取focused 這個view,當獲取到了的時候將其標記爲錨點,若是獲取不到那麼就經過findReferenceChildClosestToEnd和findReferenceChildClosestToStart去尋找合適的view,並將其標記爲錨點;

讓咱們回到onLayoutChildren這個方法,當獲取到錨點的時候,調用fill方法開始填充頁面,根據fill方法看看具體作了什麼?

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        ...
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            //回收沒有用到的view
            recycleByLayoutState(recycler, layoutState);
        }
        ...
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
              ...
        }
    }

這裏經過recycleByLayoutState方法先將沒有用到view進行回收,而後再經過while循環調用layoutChunk方法進行佈局;

看一下layoutChunk方法具體作了什麼操做?

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        ...
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
           ...
        }
        ...
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        ...
    }

到這裏就是最終佈局的地方了,先經過recycler獲取要佈局的view,再經過addView方法將view添加到RecyclerView裏去,而後根據參數調用layoutDecoratedWithMargins方法進行佈局;

public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
                int bottom) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Rect insets = lp.mDecorInsets;
            child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
                    right - insets.right - lp.rightMargin,
                    bottom - insets.bottom - lp.bottomMargin);
        }

這裏最終調用了view的layout方法進行佈局;到這裏dispatchLayoutStep2()就分析完了,讓咱們繼續接着看dispatchLayoutStep3()第三步裏面作了啥;

dispatchLayoutStep3():
private void dispatchLayoutStep3() {
        mState.assertLayoutStep(State.STEP_ANIMATIONS);
        ...
        mState.mLayoutStep = State.STEP_START;
        if (mState.mRunSimpleAnimations) {
            ...
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                if (holder.shouldIgnore()) {
                    continue;
                }
                long key = getChangedHolderKey(holder);
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPostLayoutInformation(mState, holder);
                ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
                ...
                    if (oldDisappearing && oldChangeViewHolder == holder) {
                        // run disappear animation instead of change
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                    } else {
                        ...
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                        ...
                    }
                } else {
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                }
            }

            // Step 4: Process view info lists and trigger animations
          //觸發動畫
            mViewInfoStore.process(mViewInfoProcessCallback);
        }

        ...
    }

這個方法裏面只須要關注addToPostLayout這個方法就行,這裏和第一步相似,也是經過遍歷viewholder信息來建立ItemHolderInfo,並保存到mViewInfoStore裏去;

看一下addToPostLayout這個方法作了啥?

void addToPostLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.postInfo = info;

也是經過將ItemHolderInfo信息轉化爲InfoRecord類,而後保存到集合裏去(mLayoutHolderMap);

到此,RecyclerView的onLayout流程就已經走完了;那麼接下來就要開始分析onDraw的流程了;

onDraw()分析

先看一下源碼;

public void draw(Canvas c) {
        super.draw(c);

        ...
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        ...
    }


public void onDraw(Canvas c) {
        super.onDraw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

很簡單,就幾行,mItemDecorations這個集合裏面存的是ItemDecoration,也就是說,RecyclerView的onDraw是用來繪製ItemDecoration的;而itemView的繪製是在ViewGroup裏面;

至此,RecyclerView的onMeasure,onLayout,onDraw,流程就已經分析完畢了;

總結:

RecyclerView的佈局流程比較複雜,可是仍是遵循viewGroup的繪製原理,即onMeasure,onLayout,onDraw這幾步流程;

繪製流程.png

那麼到這裏,繪製的流程就已經講完了,但願能對你有所幫助,後面會繼續分析RecyclerView的複用機制,敬請期待!

相關文章
相關標籤/搜索