Android RecyclerView 解析之繪制流程篇

前言: 當(dāng)前市場(chǎng)上有很多成熟的RecyclerView分析文章,但那始終是其他人總結(jié)出來(lái)的,還得自己動(dòng)手分析,才知道自己理解了有多少,當(dāng)然這個(gè)也算是加深對(duì)RecyclerView對(duì)理解吧;

官方簡(jiǎn)介:A flexible view for providing a limited window into a large data set.
一種靈活的視圖,在有限的窗口,展示大量的數(shù)據(jù)集;

在開始之前,為了加深理解,我們需要帶著疑問(wèn)進(jìn)行閱讀;
(1),RecyclerView是怎么加載數(shù)據(jù)的?
(2),RecyclerView是怎么將View繪制到頁(yè)面上的?
(3),RecyclerView是怎么復(fù)用item的?

1.1 總體結(jié)構(gòu)

RecyclerView主體架構(gòu).png

由上圖可知,RecyclerView主要由這幾部分組成;那他們的關(guān)系是啥呢? 具體是如何關(guān)聯(lián)的呢?且聽完細(xì)細(xì)道來(lái)!

數(shù)據(jù)層面:首頁(yè)RecyclerView需要將數(shù)據(jù)和view綁定起來(lái),是通過(guò)Adapter加載ViewHolder來(lái)實(shí)現(xiàn)綁定數(shù)據(jù)的;
布局層面:RecyclerView的Item的布局是通過(guò)LayoutManager來(lái)進(jìn)行布局的;
復(fù)用層面:LayoutManger從Recycler獲取item來(lái)進(jìn)行復(fù)用;

總結(jié):

1,Adapter:將數(shù)據(jù)轉(zhuǎn)化為RecyclerView可以識(shí)別的數(shù)據(jù);
2,ViewHolder:將數(shù)據(jù)和item綁定起來(lái);
3,LayoutManager:通過(guò)計(jì)算將Item布局到頁(yè)面中;
4,Recycler:復(fù)用機(jī)制,統(tǒng)一管理Item,用于復(fù)用;
5,ItemDecoration:繪制item的樣式;

1.2 具體流程:

1.2.1 RecyclerView 初始化流程

首先,先來(lái)看看RecyclerView 的初始化流程,先舉個(gè)簡(jiǎn)單的例子;

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

1,我們先來(lái)看看RecyclerView 的構(gòu)造方法做了啥?

public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //創(chuàng)建觀察者
        this.mObserver = new RecyclerView.RecyclerViewDataObserver();
        //創(chuàng)建回收器
        this.mRecycler = new RecyclerView.Recycler();
        //創(chuàng)建布局信息保存類
        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);
            //通過(guò)layoutManger的名稱進(jìn)行反射創(chuàng)建layoutManager,并設(shè)置給RecycleView
            this.createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
          ...
        } else {
            this.setDescendantFocusability(262144);
        }
        //設(shè)置是否支持嵌套滾動(dòng),默認(rèn)為true
        this.setNestedScrollingEnabled(nestedScrollingEnabled);
    }

從構(gòu)造方法可以看出,里面做了一大堆初始化的操作,最主要看一下這個(gè)創(chuàng)建layoutManager的方法createLayoutManager();

根據(jù)布局屬性進(jìn)行反射來(lái)創(chuàng)建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 {
                        //通過(guò)反射創(chuàng)建布局構(gòu)造器
                        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);

                     //將創(chuàng)建出來(lái)的LayoutManger設(shè)置給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()這個(gè)方法里面做了啥操作?

public void setLayoutManager(@Nullable RecyclerView.LayoutManager layout) {
        if (layout != this.mLayout) {
           //停止當(dāng)前的滾動(dòng)操作
            this.stopScroll();
            if (this.mLayout != null) {
                //判斷當(dāng)前的layoutManager如果為空,則將該layoutManager的狀態(tài)進(jìn)行初始化;
                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();
            //將當(dāng)前的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());
                }
                //將當(dāng)前的RecyclerView賦值給layoutManager
                this.mLayout.setRecyclerView(this);
                if (this.mIsAttached) {
                    this.mLayout.dispatchAttachedToWindow(this);
                }
            }
            //更新一下RecyclerView的緩存
            this.mRecycler.updateViewCacheSize();
            //觸發(fā)重新布局
            this.requestLayout();
        }
    }

總結(jié):看完RecyclerView的構(gòu)造方法,里面主要是做了一些初始化的操作,并創(chuàng)建了layoutManager設(shè)置給RecyclerView(如果布局屬性有設(shè)置的話);

2,看完了RecyclerView的setLayoutManager()的流程,我們繼續(xù)接著分析,看一下setAdapter()具體做了啥?

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

跟進(jìn)源碼,我們主要分析setAdapterInternal()這個(gè)方法,讓我們看看這個(gè)源碼里面做了什么操作;

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

        if (!compatibleWithPrevious || removeAndRecycleViews) { 
            //進(jìn)行初始化操作,初始化layoutManger,初始化mRecycler
            this.removeAndRecycleViews();
        }
        
        this.mAdapterHelper.reset();
        RecyclerView.Adapter oldAdapter = this.mAdapter;
        //將adapter賦值給當(dāng)前成員變量
        this.mAdapter = adapter;
        if (adapter != null) {
            //adapter注冊(cè)數(shù)據(jù)觀察者,用于監(jiān)聽數(shù)據(jù)的增刪改查
            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;
    }

這個(gè)方法里面主要是給adapter注冊(cè)數(shù)據(jù)監(jiān)聽,用于數(shù)據(jù)的增刪改查的刷新,并做一些初始化的操作;

我們?cè)倏匆幌逻@個(gè)觀察者里面主要做了什么操作,具體的實(shí)現(xiàn)是在RecyclerViewDataObserver 這個(gè)類里面;

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刷新數(shù)據(jù)所調(diào)用的方法;我們主要分析其中一個(gè)方法即可,讓我們來(lái)看一下onItemRangeChanged()這個(gè)方法;
這里面主要分為兩步:

public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            //這里通過(guò)AdapterHelper將傳進(jìn)來(lái)的信息保存起來(lái)
            if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                //重新布局
                this.triggerUpdateProcessor();
            }

        }

(1)通過(guò)AdapterHelper將傳進(jìn)來(lái)的信息保存起來(lái);

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)通過(guò)triggerUpdateProcessor()方法觸發(fā)RecyclerView重新布局;

void triggerUpdateProcessor() {
       if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
                //當(dāng)前有動(dòng)畫正在執(zhí)行的時(shí)候會(huì)走這里
                ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
            } else { 
                //觸發(fā)重新布局
                RecyclerView.this.mAdapterUpdateDuringMeasure = true;
                RecyclerView.this.requestLayout();
            }

        }

1,RecyclerView的主要繪制流程;
2,復(fù)用機(jī)制;

2. 工作流程

2.1 主體關(guān)系

首先我們來(lái)看一下各個(gè)模塊的關(guān)系;


關(guān)系圖.png

通過(guò)上圖大體可以看出這幾個(gè)模塊的關(guān)系:

(1)RecyclerView通過(guò)LayoutManager來(lái)進(jìn)行布局操作;
(2)LayoutManager從Recycler里面獲取復(fù)用的item來(lái)進(jìn)行布局;
(3)Recycler管理著ViewHolder的創(chuàng)建與復(fù)用;
(4)Adapter將數(shù)據(jù)和ViewHolder綁定起來(lái),并和RecyclerView注冊(cè)觀察者;
(5)RecyclerView通過(guò)ItemDecoration進(jìn)行item樣式的繪制;

接下來(lái)通過(guò)源碼來(lái)細(xì)細(xì)剖析,看看具體是怎么實(shí)現(xiàn)的;
那么我們接著上面分析的setAdapter()方法繼續(xù)分析,在setAdapter()方法里,最后調(diào)用來(lái)requestLayout(),來(lái)觸發(fā)RecyclerView 的繪制流程;
這個(gè)requestLayout()這個(gè)方法最終會(huì)調(diào)用到ViewRootImp里面的requestLayout()方法;

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

在ViewRootImp里調(diào)用requestLayout()方法進(jìn)行繪制,我們主要看scheduleTraversals()方法,里面最終會(huì)調(diào)用到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()這個(gè)方法里面執(zhí)行了三大步驟,測(cè)量(measure),布局(layout),繪制(draw),完成的view的工作流程,將頁(yè)面繪制出來(lái);

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

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

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

從上面整理的方法來(lái)看,繪制流程主要是這performMeasure(),performLayout(),performDraw();最終會(huì)觸發(fā)RecyclerView的onMeasure(),onLayout(),onDraw()方法,具體源碼這里就不過(guò)多分析了,感興趣的可以看一下View的繪制流程;

讓我們一個(gè)個(gè)來(lái)進(jìn)行分析,先看看RecyclerView的onMeasure()方法里面做了什么?

onMeasure()分析:

protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            //1.判斷當(dāng)前的LayoutManger是否為空,為空則走RecyclerView默認(rèn)測(cè)量的方法 ;
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
         //2.LayoutManger開啟自動(dòng)測(cè)量時(shí)走這里處理邏輯;
        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沒有開啟自動(dòng)測(cè)量時(shí)走這里處理邏輯;
            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
        }
    }

這里面主要分三種情況,而我們大部分情況都是走第三步,通過(guò)查看官方的LayoutManger的源碼得知,LinearLayoutManager和StaggeredGridLayoutManager都開啟了自動(dòng)測(cè)試,而GridLayoutManager繼承自LinearLayoutManager;所以,官方的LayoutManager都開啟了自動(dòng)測(cè)量,這里我們只需要關(guān)注第二步的邏輯;

從上面源碼可以看出,RecyclerView通過(guò)LayoutManger里的onMeasure()來(lái)進(jìn)行測(cè)量操作;
通過(guò)State這個(gè)類來(lái)進(jìn)行布局和測(cè)試狀態(tài)的記錄,這里的mLayoutStep 包括STEP_START、STEP_LAYOUT 、 STEP_ANIMATIONS三個(gè)狀態(tài);

從源碼分析,此時(shí)測(cè)量完畢之后,判斷當(dāng)前狀態(tài)為開始的時(shí)候(STEP_START),調(diào)用了dispatchLayoutStep1()進(jìn)行了一系列的操作,這個(gè)方法執(zhí)行完了之后,會(huì)將mLayoutStep 賦值為STEP_LAYOUT;后面就執(zhí)行了dispatchLayoutStep2(),在這個(gè)方法里將mLayoutStep 賦值為STEP_ANIMATIONS;

這里我們可以理解為,RecyclerView在測(cè)量完畢之后,就開始進(jìn)行布局了,分別執(zhí)行了dispatchLayoutStep1()和dispatchLayoutStep2()方法;到此onMeasure()分析完了;

讓我們繼續(xù)接著往下看,此時(shí)RecyclerView的onMeasure()已經(jīng)執(zhí)行完了,接下來(lái)會(huì)執(zhí)行onLayout()方法,讓我們看看這個(gè)方法里面做了啥?

onLayout()分析:

先看一下源碼

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

主要看dispatchLayout()這個(gè)方法

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();
    }

通過(guò)上面源碼可以看出,之前在onMeasure()里的這個(gè)dispatchLayoutStep2()方法里面已經(jīng)把mLayoutStep 賦值為STEP_ANIMATIONS,那么這里就會(huì)走最后一個(gè)方法dispatchLayoutStep3();如果沒有執(zhí)行STEP_START方法,那么就會(huì)依次執(zhí)行dispatchLayoutStep1(),dispatchLayoutStep2(),dispatchLayoutStep3()這幾個(gè)布局方法;讓我們來(lái)一個(gè)個(gè)分析;

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;
    }

這個(gè)方法主要做了ViewHolder信息的保存,里面通過(guò)遍歷當(dāng)前的子View,根據(jù)子view的位置信息創(chuàng)建ItemHolderInfo,并添加到 ViewInfoStore這個(gè)類里面進(jìn)行保存;
看一下ItemHolderInfo這個(gè)類;

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;
    }
}

通過(guò)源碼可以看出,在dispatchLayoutStep1()方法里會(huì)先遍歷子view,并創(chuàng)建ItemHolderInfo,然后再通過(guò)ViewInfoStore的addToPreLayout()的這個(gè)方法將ItemHolderInfo賦值給InfoRecord,再保存到mLayoutHolderMap這個(gè)集合里面;

下面我們?cè)賮?lái)分析一下dispatchLayoutStep2()這個(gè)方法里面做來(lái)啥?

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);
    }
    }

通過(guò)上面的源碼可以看出,dispatchLayoutStep2()里面就開始真正的去布局了,通過(guò)onLayoutChildre()方法進(jìn)行布局,具體的實(shí)現(xiàn)都在LayoutManager的子類里面;我們常用的LayoutManager基本上是LinearLayoutManager,那么這里我們具體來(lái)分析一下這個(gè)類里面是怎么實(shí)現(xiàn)的;

先看一下源碼:

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
      
       ...
        final View focused = getFocusedChild();
        if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                || mPendingSavedState != null) {
            mAnchorInfo.reset();
            // 獲取布局的錨點(diǎn)
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
        } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                        >= mOrientationHelper.getEndAfterPadding()
                || mOrientationHelper.getDecoratedEnd(focused)
                <= mOrientationHelper.getStartAfterPadding())) {
             ...
            // 更新錨點(diǎn)信息
            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);
          ...
        }

        ...
    }

這里把代碼簡(jiǎn)化了,我們只需要關(guān)注幾個(gè)重點(diǎn)的方法;這里的布局操作是,通過(guò)尋找布局的錨點(diǎn)(mAnchorInfo),判斷是從后往前布局還是從前往后布局,然后調(diào)用fill()方法進(jìn)行布局;

尋找布局的錨點(diǎn)是通過(guò)updateAnchorInfoForLayout(recycler, state, mAnchorInfo)這個(gè)方法

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;
        }
        ...
    }

這里我們只需要關(guān)注updateAnchorFromChildren這個(gè)方法,跟進(jìn)去看一下具體做了什么;

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;
    }

從這里的源碼可以看出,先通過(guò)getFocusedChild()去獲取focused 這個(gè)view,當(dāng)獲取到了的時(shí)候?qū)⑵錁?biāo)記為錨點(diǎn),如果獲取不到那么就通過(guò)findReferenceChildClosestToEnd和findReferenceChildClosestToStart去尋找合適的view,并將其標(biāo)記為錨點(diǎn);

讓我們回到onLayoutChildren這個(gè)方法,當(dāng)獲取到錨點(diǎn)的時(shí)候,調(diào)用fill方法開始填充頁(yè)面,根據(jù)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);
              ...
        }
    }

這里通過(guò)recycleByLayoutState方法先將沒有用到view進(jìn)行回收,然后再通過(guò)while循環(huán)調(diào)用layoutChunk方法進(jìn)行布局;

看一下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);
        ...
    }

到這里就是最終布局的地方了,先通過(guò)recycler獲取要布局的view,再通過(guò)addView方法將view添加到RecyclerView里去,然后根據(jù)參數(shù)調(diào)用layoutDecoratedWithMargins方法進(jìn)行布局;

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);
        }

這里最終調(diào)用了view的layout方法進(jìn)行布局;到這里dispatchLayoutStep2()就分析完了,讓我們繼續(xù)接著看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
          //觸發(fā)動(dòng)畫
            mViewInfoStore.process(mViewInfoProcessCallback);
        }

        ...
    }

這個(gè)方法里面只需要關(guān)注addToPostLayout這個(gè)方法就行,這里和第一步類似,也是通過(guò)遍歷viewholder信息來(lái)創(chuàng)建ItemHolderInfo,并保存到mViewInfoStore里去;

看一下addToPostLayout這個(gè)方法做了啥?

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;
        

也是通過(guò)將ItemHolderInfo信息轉(zhuǎn)化為InfoRecord類,然后保存到集合里去(mLayoutHolderMap);

到此,RecyclerView的onLayout流程就已經(jīng)走完了;那么接下來(lái)就要開始分析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);
        }
    }

很簡(jiǎn)單,就幾行,mItemDecorations這個(gè)集合里面存的是ItemDecoration,也就是說(shuō),RecyclerView的onDraw是用來(lái)繪制ItemDecoration的;而itemView的繪制是在ViewGroup里面;

至此,RecyclerView的onMeasure,onLayout,onDraw,流程就已經(jīng)分析完畢了;

總結(jié):

RecyclerView的布局流程比較復(fù)雜,但是還是遵循viewGroup的繪制原理,即onMeasure,onLayout,onDraw這幾步流程;

繪制流程.png

那么到這里,布局到流程就已經(jīng)講完了,希望能對(duì)你有所幫助,后面會(huì)繼續(xù)分析RecyclerView的復(fù)用機(jī)制,敬請(qǐng)期待!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容