RecyclerView 源碼分析(一) - RecyclerView的三大流程

*本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨(dú)家發(fā)布

??從今天開始活尊,樓主正式開始分析RecyclerView的源碼牵辣。為了閱讀RecyclerView的源碼陨闹,樓主專門去看了View的三大流程扑浸,也就是所謂的刷裝備引矩。當(dāng)然在閱讀RecyclerView的源碼時(shí)梁丘,也參考了其他大佬的文章,本文盡可能的貼出比較優(yōu)秀的文章旺韭,正所謂他山之石氛谜,可以攻玉。
??作為系列的第一篇文章区端,說說樓主為什么需要來專門的閱讀RecyclerView的源碼值漫,主要從三大方面說起。一是RecyclerView在實(shí)際開發(fā)非常的重要织盼,現(xiàn)在幾乎每個(gè)app都會展示很多的數(shù)據(jù)杨何,列表展示自然是非常好的方式,而在RecyclerView在列表中占據(jù)著舉足輕重的作用沥邻,所以RecyclerView在實(shí)際開發(fā)中危虱,是經(jīng)常見的,我們得之魚唐全,還必須得之漁埃跷;二是現(xiàn)在網(wǎng)上分析RecyclerView的文章滿篇飛,但是文章大多都比較零碎邮利,沒有系統(tǒng)的分析RecyclerView捌蚊,本文打算系統(tǒng)的分析RecyclerView,也可以說是集百家之長近弟;三是樓主本身對RecycleView的使用也是非常的頻繁缅糟,但是沒有深入的了解它的原理,所以這也算是對自身的一個(gè)提升祷愉。
??閱讀源碼本身是一件非炒盎拢枯燥和耗時(shí)間的事情,對樓主自身來說二鳄,也是亞歷山大赴涵,害怕自己自身的經(jīng)驗(yàn)不足,誤導(dǎo)前來學(xué)習(xí)的同學(xué)订讼,所以如果文章中有錯(cuò)誤的地方髓窜,請各位大佬指點(diǎn)。
??本系列文章樓主打算從幾個(gè)地方說起。先是將RecyclerView當(dāng)成一個(gè)普通的View寄纵,分別分析它的三大流程鳖敷、事件傳遞(包括嵌套滑動(dòng));然后是分析RecyclerView的緩存原理程拭,這也是RecyclerView的精華所在;然后分析的是RecyclerViewAdapter定踱、LayoutManagerItemAnimatorItemDecoration恃鞋。最后就是RecyclerView的擴(kuò)展崖媚,包括LayoutManager的自定義和使用RecyclerView常見的坑等。
??看到上面所寫的列表恤浪,自己也不禁留下冷汗畅哑,原來RecyclerView有這么多的內(nèi)容,真擔(dān)心自己不能完成任務(wù)??水由。

1. 概述

??在分析RecyclerView源碼之前荠呐,我們還是對RecyclerView有一個(gè)初步的了解,簡單的了解它是什么绷杜,它的基本結(jié)構(gòu)有哪些。
??RecyclerView是Google爸爸在2014年的IO大會提出來(看來RecyclerView的年齡還是比較大了??)濒募,具體目的是不是用來替代ListView的鞭盟,樓主也不知道,因?yàn)槟菚r(shí)候樓主還在讀高二瑰剃。但是在實(shí)際開發(fā)中齿诉,自從有了RecyclerViewListViewGridView就很少用了晌姚,所以我們暫且認(rèn)為RecyclerView的目的是替代ListViewGridView粤剧。
??RecyclerView本身是一個(gè)展示大量數(shù)據(jù)的控件,相比較ListView,RecyclerView的4級緩存(也有人說是3級緩存挥唠,這些都不重要??)就表現(xiàn)的非常出色抵恋,在性能方面相比于ListView提升了不少。同時(shí)由于LayoutManager的存在,讓RecyclerView不僅有ListView的特點(diǎn)宝磨,同時(shí)兼有GridView的特點(diǎn)弧关。這可能是RecyclerView受歡迎的原因之一吧。
??RecyclerView在設(shè)計(jì)方面上也是非常的靈活唤锉,不同的部分承擔(dān)著不同的職責(zé)世囊。其中Adapter負(fù)責(zé)提供數(shù)據(jù),包括創(chuàng)建ViewHolder和綁定數(shù)據(jù)窿祥,LayoutManager負(fù)責(zé)ItemView的測量和布局,ItemAnimator負(fù)責(zé)每個(gè)ItemView的動(dòng)畫株憾,ItemDecoration負(fù)責(zé)每個(gè)ItemView的間隙。這種插拔式的架構(gòu)使得RecyclerView變得非常的靈活晒衩,每一個(gè)人都可以根據(jù)自身的需求來定義不同的部分嗤瞎。
??正因?yàn)檫@種插拔式的設(shè)計(jì)墙歪,使得RecyclerView在使用上相比較于其他的控件稍微難那么一點(diǎn)點(diǎn),不過這都不算事猫胁,誰叫RecyclerView這么惹人愛呢??箱亿。


??好了,好像廢話有點(diǎn)多弃秆,現(xiàn)在我們正式來分析源碼吧届惋,本文的重點(diǎn)是RecyclerView的三大流程。
??本文參考文章:

  1. 【進(jìn)階】RecyclerView源碼解析(一)——繪制流程
  2. RecyclerView剖析
  3. RecyclerView剖析——續(xù)一

??注意菠赚,本文RecyclerView源碼均來自于27.1.1

2. measure

??不管RecyclerView是多么神奇脑豹,它也是一個(gè)View,所以分析它的三大流程是非常有必要的衡查。同時(shí)瘩欺,如果了解過RecyclerView的同學(xué)應(yīng)該都知道,RecyclerView的三大流程跟普通的View比較拌牲,有很大的不同俱饿。
??首先,我們來看看measure過程塌忽,來看看RecyclerViewonMeasure方法拍埠。

    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            // 第一種情況
        }
        if (mLayout.isAutoMeasureEnabled()) {
            // 第二種情況
        } else {
            // 第三種情況
        }
    }

??onMeasure方法還是有點(diǎn)長,這里我將它分為3種情況土居,我將簡單解釋這三種情況枣购。

  1. mLayoutLayoutManager的對象。我們知道擦耀,當(dāng)RecyclerViewLayoutManager為空時(shí),RecyclerView不能顯示任何的數(shù)據(jù)棉圈,在這里我們找到答案。
  2. LayoutManager開啟了自動(dòng)測量時(shí)眷蜓,這是一種情況分瘾。在這種情況下,有可能會測量兩次吁系。
  3. 第三種情況就是沒有開啟自動(dòng)測量的情況芹敌,這種情況比較少,因?yàn)闉榱?code>RecyclerView支持warp_content屬性垮抗,系統(tǒng)提供的LayoutManager都開啟自動(dòng)測量的氏捞,不過我們還是要分析的。

??首先我們來第一種情況冒版。

(1).當(dāng)LayoutManager為空時(shí)

??這種情況下比較簡單液茎,我們來看看源碼:

        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }

??直接調(diào)了defaultOnMeasure方法,我們繼續(xù)來看defaultOnMeasure方法。

    void defaultOnMeasure(int widthSpec, int heightSpec) {
        // calling LayoutManager here is not pretty but that API is already public and it is better
        // than creating another method since this is internal.
        final int width = LayoutManager.chooseSize(widthSpec,
                getPaddingLeft() + getPaddingRight(),
                ViewCompat.getMinimumWidth(this));
        final int height = LayoutManager.chooseSize(heightSpec,
                getPaddingTop() + getPaddingBottom(),
                ViewCompat.getMinimumHeight(this));

        setMeasuredDimension(width, height);
    }

??在defaultOnMeasure方法里面捆等,先是通過LayoutManagerchooseSize方法來計(jì)算值滞造,然后就是setMeasuredDimension方法來設(shè)置寬高。我們來看看:

        public static int chooseSize(int spec, int desired, int min) {
            final int mode = View.MeasureSpec.getMode(spec);
            final int size = View.MeasureSpec.getSize(spec);
            switch (mode) {
                case View.MeasureSpec.EXACTLY:
                    return size;
                case View.MeasureSpec.AT_MOST:
                    return Math.min(size, Math.max(desired, min));
                case View.MeasureSpec.UNSPECIFIED:
                default:
                    return Math.max(desired, min);
            }
        }

??chooseSize方法表達(dá)的意思比較簡單栋烤,就是通過RecyclerView的測量mode來獲取不同的值谒养,這里就不詳細(xì)的解釋了。
??到此明郭,第一種情況就分析完畢了买窟。因?yàn)楫?dāng)LayoutManager為空時(shí),那么當(dāng)RecyclerView處于onLayout階段時(shí)薯定,會調(diào)用dispatchLayout方法始绍。而在dispatchLayout方法里面有這么一行代碼:

        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }

??所以,當(dāng)LayoutManager為空時(shí)话侄,不顯示任何數(shù)據(jù)是理所當(dāng)然的亏推。
??現(xiàn)在我們來看看第二種情況,也就是正常的情況年堆。

(2). 當(dāng)LayoutManager開啟了自動(dòng)測量

??在分析這種情況之前吞杭,我們先對了解幾個(gè)東西。
??RecyclerView的測量分為兩步变丧,分別調(diào)用dispatchLayoutStep1dispatchLayoutStep2芽狗。同時(shí),了解過RecyclerView源碼的同學(xué)應(yīng)該知道在RecyclerView的源碼里面還一個(gè)dispatchLayoutStep3方法锄贷。這三個(gè)方法的方法名比較接近译蒂,所以容易讓人搞混淆曼月。本文會詳細(xì)的講解這三個(gè)方法的作用谊却。
??由于在這種情況下,只會調(diào)用dispatchLayoutStep1dispatchLayoutStep2這兩個(gè)方法哑芹,所以這里會重點(diǎn)的講解這兩個(gè)方法炎辨。而dispatchLayoutStep3方法的調(diào)用在RecyclerViewonLayout方法里面,所以在后面分析onLayout方法時(shí)再來看dispatchLayoutStep3方法聪姿。
??我們在分析之前碴萧,先來看一個(gè)東西--mState.mLayoutStep。這個(gè)變量有幾個(gè)取值情況末购。我們分別來看看:

取值 含義
State.STEP_START mState.mLayoutStep的默認(rèn)值破喻,這種情況下,表示RecyclerView還未經(jīng)歷dispatchLayoutStep1盟榴,因?yàn)?code>dispatchLayoutStep1調(diào)用之后mState.mLayoutStep會變?yōu)?code>State.STEP_LAYOUT曹质。
State.STEP_LAYOUT 當(dāng)mState.mLayoutStepState.STEP_LAYOUT時(shí),表示此時(shí)處于layout階段,這個(gè)階段會調(diào)用dispatchLayoutStep2方法layout RecyclerViewchildren羽德。調(diào)用dispatchLayoutStep2方法之后几莽,此時(shí)mState.mLayoutStep變?yōu)榱?code>State.STEP_ANIMATIONS。
State.STEP_ANIMATIONS 當(dāng)mState.mLayoutStepState.STEP_ANIMATIONS時(shí)宅静,表示RecyclerView處于第三個(gè)階段章蚣,也就是執(zhí)行動(dòng)畫的階段,也就是調(diào)用dispatchLayoutStep3方法姨夹。當(dāng)dispatchLayoutStep3方法執(zhí)行完畢之后纤垂,mState.mLayoutStep又變?yōu)榱?code>State.STEP_START。

??從上表中匀伏,我們了解到mState.mLayoutStep的三個(gè)狀態(tài)對應(yīng)著不同的dispatchLayoutStep方法洒忧。這一點(diǎn),我們必須清楚够颠,否則接下來的代碼將難以理解熙侍。
??好了,前戲準(zhǔn)備的差不多履磨,現(xiàn)在應(yīng)該進(jìn)入高潮了??蛉抓。我們開始正式的分析源碼了。

        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            /**
             * This specific call should be considered deprecated and replaced with
             * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
             * break existing third party code but all documentation directs developers to not
             * override {@link LayoutManager#onMeasure(int, int)} when
             * {@link LayoutManager#isAutoMeasureEnabled()} returns true.
             */
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || 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);
            }
        }

??我將這段代碼分為三步剃诅。我們來看看:

  1. 調(diào)用LayoutManageronMeasure方法進(jìn)行測量巷送。對于onMeasure方法,我也感覺到非常的迷惑矛辕,發(fā)現(xiàn)傳統(tǒng)的LayoutManager都沒有實(shí)現(xiàn)這個(gè)方法笑跛。后面,我們會將簡單的看一下這個(gè)方法聊品。
  2. 如果mState.mLayoutStepState.STEP_START的話飞蹂,那么就會執(zhí)行dispatchLayoutStep1方法,然后會執(zhí)行dispatchLayoutStep2方法翻屈。
  3. 如果需要第二次測量的話陈哑,會再一次調(diào)用dispatchLayoutStep2 方法。

??以上三步伸眶,我們一步一步的來分析惊窖。首先,我們來看看第一步厘贼,也是看看onMeasure方法界酒。
??LayoutManageronMeasure方法究竟為我們做什么,我們來看看:

        public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
            mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
        }

??默認(rèn)是調(diào)用的RecyclerViewdefaultOnMeasure方法,至于defaultOnMeasure方法里面究竟做了什么嘴秸,這在前面已經(jīng)介紹過了毁欣,這里就不再介紹了售担。
??ViewonMeasure方法的作用通產(chǎn)來說有兩個(gè)。一是測量自身的寬高署辉,從RecyclerView來看族铆,它將自己的測量工作托管給了LayoutManageronMeasure方法。所以哭尝,我們在自定義LayoutManager時(shí)哥攘,需要注意onMeasure方法存在,不過從官方提供的幾個(gè)LayoutManager材鹦,都沒有重寫這個(gè)方法逝淹。所以不到萬得已,最好不要重寫LayoutManageronMeasure方法桶唐;二是測量子View,不過到這里我們還沒有看到具體的實(shí)現(xiàn)栅葡。
??接下來,我們來分析第二步尤泽,看看dispatchLayoutStep1方法和dispatchLayoutStep2方法究竟做了什么欣簇。
??在正式分析第二步之前,我們先對這三個(gè)方法有一個(gè)大概的認(rèn)識坯约。

方法名 作用
dispatchLayoutStep1 三大dispatchLayoutStep方法第一步熊咽。本方法的作用主要有三點(diǎn):1.處理Adapter更新;2.決定是否執(zhí)行ItemAnimator;3.保存ItemView的動(dòng)畫信息。本方法也被稱為preLayout(預(yù)布局)闹丐,當(dāng)Adapter更新了横殴,這個(gè)方法會保存每個(gè)ItemView的舊信息(oldViewHolderInfo)
dispatchLayoutStep2 三大dispatchLayoutStep方法第二步。在這個(gè)方法里面卿拴,真正進(jìn)行children的測量和布局衫仑。
dispatchLayoutStep3 三大dispatchLayoutStep方法第三步。這個(gè)方法的作用執(zhí)行在dispatchLayoutStep1方法里面保存的動(dòng)畫信息堕花。本方法不是本文的介紹重點(diǎn)文狱,后面在介紹ItemAnimator時(shí),會重點(diǎn)分析這個(gè)方法航徙。

??我們回到onMeasure方法里面如贷,先看看整個(gè)執(zhí)行過程陷虎。

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

??如果mState.mLayoutStep == State.STEP_START時(shí)到踏,才會調(diào)用 dispatchLayoutStep1方法,這里與我們前面介紹mLayoutStep對應(yīng)起來了∩性常現(xiàn)在我們看看dispatchLayoutStep1方法

    private void dispatchLayoutStep1() {
        mState.assertLayoutStep(State.STEP_START);
        fillRemainingScrollValues(mState);
        mState.mIsMeasuring = false;
        startInterceptRequestLayout();
        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) {
           // 找到?jīng)]有被remove的ItemView,保存OldViewHolder信息窝稿,準(zhǔn)備預(yù)布局
        }
        if (mState.mRunPredictiveAnimations) {
           // 進(jìn)行預(yù)布局
        } else {
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

??本文只簡單分析一下這個(gè)方法,因?yàn)檫@個(gè)方法跟ItemAnimator有莫大的關(guān)系凿掂,后續(xù)在介紹ItemAnimator時(shí)會詳細(xì)的分析伴榔。在這里纹蝴,我們將重點(diǎn)放在processAdapterUpdatesAndSetAnimationFlags里面,因?yàn)檫@個(gè)方法計(jì)算了mRunSimpleAnimationsmRunPredictiveAnimations踪少。

    private void processAdapterUpdatesAndSetAnimationFlags() {
        if (mDataSetHasChangedAfterLayout) {
            // Processing these items have no value since data set changed unexpectedly.
            // Instead, we just reset it.
            mAdapterHelper.reset();
            if (mDispatchItemsChangedEvent) {
                mLayout.onItemsChanged(this);
            }
        }
        // simple animations are a subset of advanced animations (which will cause a
        // pre-layout step)
        // If layout supports predictive animations, pre-process to decide if we want to run them
        if (predictiveItemAnimationsEnabled()) {
            mAdapterHelper.preProcess();
        } else {
            mAdapterHelper.consumeUpdatesInOnePass();
        }
        boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
        mState.mRunSimpleAnimations = mFirstLayoutComplete
                && mItemAnimator != null
                && (mDataSetHasChangedAfterLayout
                || animationTypeSupported
                || mLayout.mRequestedSimpleAnimations)
                && (!mDataSetHasChangedAfterLayout
                || mAdapter.hasStableIds());
        mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
                && animationTypeSupported
                && !mDataSetHasChangedAfterLayout
                && predictiveItemAnimationsEnabled();
    }

??這里我們的重心放在mFirstLayoutComplete變量里面塘安,我們發(fā)現(xiàn)mRunSimpleAnimations的值與mFirstLayoutComplete有關(guān),mRunPredictiveAnimations同時(shí)跟mRunSimpleAnimations有關(guān)援奢。所以這里我們可以得出一個(gè)結(jié)論,當(dāng)RecyclerView第一次加載數(shù)據(jù)時(shí),是不會執(zhí)行的動(dòng)畫。換句話說擦秽,每個(gè)ItemView還沒有layout完畢板乙,怎么會進(jìn)行動(dòng)畫。這一點(diǎn)具篇,我們也可以通過Demo來證明纬霞,這里也就不展示了。
??接下來我們看看dispatchLayoutStep2方法驱显,這個(gè)方法是真正布局children诗芜。我們來看看:

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

??在這里,我們重點(diǎn)的看兩行代碼埃疫。一是在這里绢陌,我們可以看到AdaptergetItemCount方法被調(diào)用;二是調(diào)用了LayoutManageronLayoutChildren方法,這個(gè)方法里面進(jìn)行對children的測量和布局熔恢,同時(shí)這個(gè)方法也是這里的分析重點(diǎn)脐湾。
??系統(tǒng)的LayoutManageronLayoutChildren方法是一個(gè)空方法,所以需要LayoutManager的子類自己來實(shí)現(xiàn)叙淌。從這里秤掌,我們可以得出兩個(gè)點(diǎn)。

  1. 子類LayoutManager需要自己實(shí)現(xiàn)onLayoutChildren方法鹰霍,從而來決定RecyclerView在該LayoutManager的策略下闻鉴,應(yīng)該怎么布局。從這里茂洒,我們看出來RecyclerView的靈活性孟岛。
  2. LayoutManager類似于ViewGroup,將onLayoutChildren方法(ViewGrouponLayout方法)公開出來,這種模式在Android中很常見的督勺。

??這里渠羞,我先不對onLayoutChildren方法進(jìn)行展開,待會會詳細(xì)的分析智哀。
??接下來次询,我們來分析第三種情況--沒有開啟自動(dòng)測量

(3).沒有開啟自動(dòng)測量

??我們先來看看這一塊的代碼瓷叫。

            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // custom onMeasure
            if (mAdapterUpdateDuringMeasure) {
                startInterceptRequestLayout();
                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;
                stopInterceptRequestLayout(false);
            } else if (mState.mRunPredictiveAnimations) {
                // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
                // this means there is already an onMeasure() call performed to handle the pending
                // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
                // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
                // because getViewForPosition() will crash when LM uses a child to measure.
                setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
                return;
            }

            if (mAdapter != null) {
                mState.mItemCount = mAdapter.getItemCount();
            } else {
                mState.mItemCount = 0;
            }
            startInterceptRequestLayout();
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            stopInterceptRequestLayout(false);
            mState.mInPreLayout = false; // clear

??例如上面的代碼屯吊,我將分為2步:

  1. 如果mHasFixedSize為true(也就是調(diào)用了setHasFixedSize方法)送巡,將直接調(diào)用LayoutManageronMeasure方法進(jìn)行測量。
  2. 如果mHasFixedSize為false盒卸,同時(shí)此時(shí)如果有數(shù)據(jù)更新骗爆,先處理數(shù)據(jù)更新的事務(wù),然后調(diào)用LayoutManageronMeasure方法進(jìn)行測量

??通過上面的描述蔽介,我們知道淮腾,如果未開啟自動(dòng)測量,那么肯定會調(diào)用LayoutManageronMeasure方法來進(jìn)行測量屉佳,這就是LayoutManageronMeasure方法的作用谷朝。
??至于onMeasure方法怎么進(jìn)行測量,那就得看LayoutManager的實(shí)現(xiàn)類武花。在這里圆凰,我們就不進(jìn)行深入的追究了。

3. layout

??measure過程分析的差不多了体箕,接下來我們就該分析第二個(gè)過程--layout专钉。我們來看看onLayout方法:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }

?? onLayout方法本身沒有做多少的事情,重點(diǎn)還是在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();
    }

??dispatchLayout方法也是非常的簡單跃须,這個(gè)方法保證RecyclerView必須經(jīng)歷三個(gè)過程--dispatchLayoutStep1dispatchLayoutStep2娃兽、dispatchLayoutStep3菇民。
??同時(shí),在后面的文章中投储,你會看到dispatchLayout方法其實(shí)還為RecyclerView節(jié)省了很多步驟第练,也就是說,在RecyclerView經(jīng)歷一次完整的dispatchLayout之后玛荞,后續(xù)如果參數(shù)有所變化時(shí)娇掏,可能只會經(jīng)歷最后的1步或者2步。當(dāng)然這些都是后話了??勋眯。
??對于dispatchLayoutStep1dispatchLayoutStep2方法婴梧,我們前面已經(jīng)講解了,這里就不做過多的解釋了客蹋。這里塞蹭,我們就簡單的看一下dispatchLayoutStep3方法吧。

    private void dispatchLayoutStep3() {
        // ······
        mState.mLayoutStep = State.STEP_START;
        // ······
    }

??為什么這里只是簡單看一下dispatchLayoutStep3方法呢嚼酝?因?yàn)檫@個(gè)方法主要是做Item的動(dòng)畫浮还,也就是我們熟知的ItemAnimator的執(zhí)行竟坛,而本文不對動(dòng)畫進(jìn)行展開闽巩,所以先省略動(dòng)畫部分钧舌。
??在這里,我們需要關(guān)注dispatchLayoutStep3方法的是涎跨,它將mLayoutStep重置為了State.STEP_START洼冻。也就是說如果下一次重新開始dispatchLayout的話,那么肯定會經(jīng)歷dispatchLayoutStep1隅很、dispatchLayoutStep2撞牢、dispatchLayoutStep3三個(gè)方法。
??以上就是RecyclerView的layout過程叔营,是不是感覺非常的簡單屋彪?RecyclerView跟其他ViewGroup不同的地方在于,如果開啟了自動(dòng)測量绒尊,在measure階段畜挥,已經(jīng)將Children布局完成了;如果沒有開啟自動(dòng)測量婴谱,則在layout階段才布局Children蟹但。

4. draw

??接下來,我們來分析三大流程的最后一個(gè)階段--draw谭羔。在正式分析draw過程之前华糖,我先來對RecyclerViewdraw做一個(gè)概述。
??RecyclerView分為三步瘟裸,我們來看看:

  1. 調(diào)用super.draw方法客叉。這里主要做了兩件事:1. 將Children的繪制分發(fā)給ViewGroup;2. 將分割線的繪制分發(fā)給ItemDecoration
  2. 如果需要的話话告,調(diào)用ItemDecorationonDrawOver方法十办。通過這個(gè)方法,我們在每個(gè)ItemView上面畫上很多東西超棺。
  3. 如果RecyclerView調(diào)用了setClipToPadding,會實(shí)現(xiàn)一種特殊的滑動(dòng)效果--每個(gè)ItemView可以滑動(dòng)到padding區(qū)域向族。

??我們來看看這部分的代碼:

    public void draw(Canvas c) {
        // 第一步
        super.draw(c);
        // 第二步
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        // 第三步
        // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
        // need find children closest to edges. Not sure if it is worth the effort.
        // ······
    }

??熟悉三大流程的同學(xué),肯定知道第一步會回調(diào)到onDraw方法里面棠绘,而ItemDecoration的繪制就是在onDraw方法里面件相。

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

??onDraw方法是不是非常的簡單?onDraw方法主要是將ItemDecoration的繪制分發(fā)到ItemDecorationonDraw方法里面去氧苍。從這里夜矗,我們可以看出來,RecyclerView的設(shè)計(jì)實(shí)在是太靈活了让虐!
??至于其余兩步都比較簡單紊撕,這里就不詳細(xì)分析了。不過赡突,從這里对扶,我們終于明白了ItemDecorationonDraw方法和onDrawOver方法的區(qū)別区赵。

5. LayoutManager的onLayoutChildren方法

??從整體來說,RecyclerView的三大流程還是比較簡單浪南,不過在整個(gè)過程中笼才,我們似乎忽略了一個(gè)過程--那就是RecyclerView到底是怎么layout children的?
??前面在介紹dispatchLayoutStep2方法時(shí)络凿,只是簡單的介紹了骡送,RecyclerView通過調(diào)用LayoutManageronLayoutChildren方法。LayoutManager本身對這個(gè)方法沒有進(jìn)行實(shí)現(xiàn)絮记,所以必須得看看它的子類摔踱,這里我們就來看看LinearLayoutManager
??由于LinearLayoutManageronLayoutChildren方法比較長怨愤,這里不可能貼出完整的代碼昌渤,所以這里我先對這個(gè)方法做一個(gè)簡單的概述,方便大家理解憔四。

  1. 確定錨點(diǎn)的信息膀息,這里面的信息包括:1.Children的布局方向,有start和end兩個(gè)方向了赵;2. mPositionmCoordinate潜支,分別表示Children開始填充的position和坐標(biāo)。
  2. 調(diào)用detachAndScrapAttachedViews方法柿汛,detach掉或者removeRecyclerViewChildren冗酿。這一點(diǎn)本來不在本文的講解范圍內(nèi),但是為了后續(xù)對RecyclerView的緩存機(jī)制有更好的了解络断,這里特別的提醒一下裁替。
  3. 根據(jù)錨點(diǎn)信息,調(diào)用fill方法進(jìn)行Children的填充貌笨。這個(gè)過程中根據(jù)錨點(diǎn)信息的不同弱判,可能會調(diào)用兩次fill方法。

??接下來锥惋,我們看看代碼:

    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.
        // create layout state
        // ······
        // 第一步
        final View focused = getFocusedChild();
        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
                || mPendingSavedState != null) {
            mAnchorInfo.reset();
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // calculate anchor position and coordinate
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
        }
        // ······
        // 第二步
        detachAndScrapAttachedViews(recycler);
        mLayoutState.mIsPreLayout = state.isPreLayout();
        // 第三步
        if (mAnchorInfo.mLayoutFromEnd) {
            // fill towards start
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
            final int firstElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForEnd += mLayoutState.mAvailable;
            }
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;

            if (mLayoutState.mAvailable > 0) {
                // end could not consume all. add more items towards start
                extraForStart = mLayoutState.mAvailable;
                updateLayoutStateToFillStart(firstElement, startOffset);
                mLayoutState.mExtra = extraForStart;
                fill(recycler, mLayoutState, state, false);
                startOffset = mLayoutState.mOffset;
            }
        } else {
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
            final int lastElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // fill towards start
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;

            if (mLayoutState.mAvailable > 0) {
                extraForEnd = mLayoutState.mAvailable;
                // start could not consume all it should. add more items towards end
                updateLayoutStateToFillEnd(lastElement, endOffset);
                mLayoutState.mExtra = extraForEnd;
                fill(recycler, mLayoutState, state, false);
                endOffset = mLayoutState.mOffset;
            }
        }
        // ······
    }

??相信從上面的代碼都可以找出每一步的執(zhí)行〔現(xiàn)在,我們來詳細(xì)分析每一步膀跌。首先來看第一步--確定錨點(diǎn)的信息遭商。
??要想看錨點(diǎn)信息的計(jì)算過程,我們可以從updateAnchorInfoForLayout方法里面來找出答案捅伤,我們來看看updateAnchorInfoForLayout方法:

    private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
            AnchorInfo anchorInfo) {
        // 第一種計(jì)算方式
        if (updateAnchorFromPendingData(state, anchorInfo)) {
            return;
        }
        // 第二種計(jì)算方式
        if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
            return;
        }
        // 第三種計(jì)算方式
        anchorInfo.assignCoordinateFromPadding();
        anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
    }

??我相信通過上面的代碼注釋劫流,大家都能明白updateAnchorInfoForLayout方法到底干了嘛,這里我簡單分析一下這三種確定所做的含義,具體是怎么做的祠汇,這里就不討論仍秤,因?yàn)檫@里面的細(xì)節(jié)太多了,深入的討論容易將我們聰明無比的大腦搞暈??座哩。

  1. 第一種計(jì)算方式徒扶,表示含義有兩種:1.RecyclerView被重建粮彤,期間回調(diào)了onSaveInstanceState方法根穷,所以目的是為了恢復(fù)上次的布局;2.RecyclerView調(diào)用了scrollToPosition之類的方法导坟,所以目的是讓
    RecyclerView滾到準(zhǔn)確的位置上去屿良。所以,錨點(diǎn)的信息根據(jù)上面的兩種情況來計(jì)算惫周。
  2. 第二種計(jì)算方法尘惧,從Children上面來計(jì)算錨點(diǎn)信息。這種計(jì)算方式也有兩種情況:1. 如果當(dāng)前有擁有焦點(diǎn)的Child递递,那么有當(dāng)前有焦點(diǎn)的Child的位置來計(jì)算錨點(diǎn)喷橙;2. 如果沒有child擁有焦點(diǎn),那么根據(jù)布局方向(此時(shí)布局方向由mLayoutFromEnd來決定)獲取可見的第一個(gè)ItemView或者最后一個(gè)ItemView登舞。
  3. 如果前面兩種方式都計(jì)算失敗了贰逾,那么采用第三種計(jì)算方式,也就是默認(rèn)的計(jì)算方式菠秒。

??以上就是updateAnchorInfoForLayout方法所做的事情疙剑,這里就不詳細(xì)糾結(jié)每種計(jì)算方式的細(xì)節(jié),有興趣的同學(xué)可以看看践叠。
??至于第二步言缤,調(diào)用detachAndScrapAttachedViews方法對所有的ItemView進(jìn)行回收,這部分的內(nèi)容屬于RecyclerView緩存機(jī)制的部分,本文先在這里埋下一個(gè)伏筆禁灼,后續(xù)專門講解RecyclerView會詳細(xì)的分析它管挟,所以這里就不講解了。
??接下來我們來看看第三步弄捕,也就是調(diào)用fill方法來填充Children哮独。在正式分析填充過程時(shí),我們先來看一張圖片:


??圖片的原圖出自RecyclerView剖析,如有侵權(quán)察藐,請聯(lián)系我皮璧。
??上圖形象的展現(xiàn)出三種fill的情況。其中分飞,我們可以看到第三種情況悴务,fill方法被調(diào)用了兩次。
??我們看看fill方法:

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        // ······
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            // ······
            layoutChunk(recycler, state, layoutState, layoutChunkResult);

        }
         // ······
    }

??fill方法的代碼比較長,其實(shí)都是來計(jì)算可填充的空間讯檐,真正填充Child的地方是layoutChunk方法羡疗。我們來看看layoutChunk方法。
??由于layoutChunk方法比較長别洪,這里我就不完整的展示叨恨,為了方便理解,我對這個(gè)方法做一個(gè)簡單的概述挖垛,讓大家有一個(gè)大概的理解痒钝。

  1. 調(diào)用LayoutStatenext方法獲得一個(gè)ItemView。千萬別小看這個(gè)next方法痢毒,RecyclerView緩存機(jī)制的起點(diǎn)就是從這個(gè)方法開始送矩,可想而知,這個(gè)方法到底為我們做了多少事情哪替。
  2. 如果RecyclerView是第一次布局Children的話(layoutState.mScrapList == null為true)栋荸,會先調(diào)用addView,將View添加到RecyclerView里面去凭舶。
  3. 調(diào)用measureChildWithMargins方法晌块,測量每個(gè)ItemView的寬高。注意這個(gè)方法測量ItemView的寬高考慮到了兩個(gè)因素:1.margin屬性帅霜;2.ItemDecorationoffset匆背。
  4. 調(diào)用layoutDecoratedWithMargins方法,布局ItemView义屏。這里也考慮上面的兩個(gè)因素的靠汁。

??至于每一步具體干了嘛,這里就不詳細(xì)的解釋闽铐,都是一些基本操作蝶怔,有興趣的同學(xué)可以看看。
??綜上所述兄墅,便是LayoutManageronLayoutChildren方法整個(gè)執(zhí)行過程踢星,思路還是比較簡單的。

6. 總結(jié)

??本文到此就差不多了隙咸,在最后沐悦,我做一個(gè)簡單的總結(jié)。

  1. RecyclerViewmeasure過程分為三種情況五督,每種情況都有執(zhí)行過程藏否。通常來說,我們都會走自動(dòng)測量的過程充包。
  2. 自動(dòng)測量里面需要分清楚mState.mLayoutStep狀態(tài)值副签,因?yàn)楦鶕?jù)不同的狀態(tài)值調(diào)用不同的dispatchLayoutStep方法遥椿。
  3. layout過程也根據(jù)mState.mLayoutStep狀態(tài)來調(diào)用不同的dispatchLayoutStep方法
  4. draw過程主要做了四件事:1.繪制ItemDecorationonDraw部分;2.繪制Children;3.繪制ItemDecorationdrawOver部分;4. 根據(jù)mClipToPadding的值來判斷是否進(jìn)行特殊繪制。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淆储,一起剝皮案震驚了整個(gè)濱河市冠场,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌本砰,老刑警劉巖碴裙,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異点额,居然都是意外死亡舔株,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門咖楣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來督笆,“玉大人芦昔,你說我怎么就攤上這事诱贿。” “怎么了咕缎?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵珠十,是天一觀的道長。 經(jīng)常有香客問我凭豪,道長焙蹭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任嫂伞,我火速辦了婚禮孔厉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帖努。我一直安慰自己撰豺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布拼余。 她就那樣靜靜地躺著污桦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪匙监。 梳的紋絲不亂的頭發(fā)上凡橱,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音亭姥,去河邊找鬼稼钩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛达罗,可吹牛的內(nèi)容都是我干的坝撑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绍载!你這毒婦竟也來了诡宗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤击儡,失蹤者是張志新(化名)和其女友劉穎塔沃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阳谍,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛀柴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了矫夯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸽疾。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖训貌,靈堂內(nèi)的尸體忽然破棺而出制肮,到底是詐尸還是另有隱情,我是刑警寧澤递沪,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布豺鼻,位于F島的核電站,受9級特大地震影響款慨,放射性物質(zhì)發(fā)生泄漏儒飒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一檩奠、第九天 我趴在偏房一處隱蔽的房頂上張望桩了。 院中可真熱鬧,春花似錦埠戳、人聲如沸井誉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽送悔。三九已至,卻和暖如春爪模,著一層夾襖步出監(jiān)牢的瞬間欠啤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工屋灌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洁段,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓共郭,卻偏偏與公主長得像祠丝,于是被迫代替她去往敵國和親疾呻。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345