Android 源碼分析 - View的measure县貌、layout术陶、draw三大流程

??經(jīng)過兩個多月的框架源碼轟炸,感覺自己的腦子變得有點懵逼了煤痕。在這兩個多月里面梧宫,先后看了RxJava、OkHttp和Retrofit的源碼摆碉,并且將自己的理解寫成了博客塘匣,作為記錄;后續(xù)又看了EventBus和ButterKnife的源碼巷帝,本來都想寫成博客的忌卤,但是覺得這兩個框架比較簡單,因此就沒有寫(純粹個人想法楞泼,大家有意見的話驰徊,盡管噴);最后现拒,就是簡單的看了一下Glide的源碼辣垒,太特么的難了,看不懂看不懂印蔬,看到一半就放棄了勋桶,應(yīng)該是自己的功力不夠,自己再沉淀沉淀侥猬,之后再去試試吧例驹。
??今天,我將帶來一篇比較輕松的文章--View的mesure退唠、layout鹃锈、draw三大流程。本文將詳細講解View的三大流程瞧预,閱讀本文最好有牢固的Android基礎(chǔ)屎债,并且對Android View的基本結(jié)構(gòu)有所了解。
??說到寫本文的經(jīng)歷還有點曲折垢油,本來一開始打算好好的寫這篇文章盆驹,但是寫著寫著感覺沒什么寫的,然后自己轉(zhuǎn)而去看RecyclerView的源碼滩愁,將RecyclerView的三大流程簡單的梳理完畢之后躯喇,發(fā)現(xiàn)RecyclerView的三大流程跟普通的View有很大的不同,所以決定重新來寫這篇文章硝枉。說到底廉丽,本文就是為了后面的RecyclerView源碼打基礎(chǔ)??倦微。
??好了,廢話少說正压,進入正文欣福。本文參考資料:
??1. Android View源碼解讀:淺談DecorView與ViewRootImpl
??2. Android View 測量流程(Measure)完全解析
??3. 從requestLayout()初探View的繪制原理
??4.Android View 繪制流程(Draw) 完全解析
??5. 任玉剛大神的《Android開發(fā)藝術(shù)探索》
??注意:本文所有源碼都基于 API 27。

1. 概述

??View的三大流程非常的重要蔑匣,重要到那種程度呢劣欢?幾乎達到了面試必問的程度棕诵,同時裁良,在實際的開發(fā)中,如果熟悉三大流程的話校套,自定義View可以寫的非常6价脾,當然在解決那種迷之問題時,熟悉三大流程必將事倍功半笛匙。
??View的三大流程侨把,分別是measure、layout妹孙、draw三個過程秋柄。我想,不用解釋這三大流程分別是干嘛的吧蠢正?咱們從它的英文意思上就可以知道骇笔。
??在正式分析源碼之前,我們先來通過一張圖片對三大流程有一個整體的了解嚣崭。


??上面的流程圖從大概上解釋了三大流程的過程笨触,但是很多的細節(jié)都沒有解釋到,這就需要我們從源碼的程度來分析了雹舀。接下來芦劣,我們正式進入View三大流程的源碼分析。

2. ViewRootImpl

??View的三大流程從ViewRootImplperformTraversals方法開始的说榆,具體是怎么調(diào)用的這個方法來的虚吟,這里就不詳細的解釋了,因為這里面涉及到Activity的創(chuàng)建签财、setContentView串慰、PhoneWindow等等。這里我們只需要知道荠卷,performTraversals方法就是三大流程的開始模庐。但是整個過程是怎么傳遞下去的呢?這個我們必須得對整個Activity的布局結(jié)構(gòu)有一個整體的認識油宜,我們來看看掂碱。
??由于這部分的知識不是本文的核心內(nèi)容怜姿,所以這里就不貼出源碼來展示了。我就簡單的解釋一下疼燥。
??每一個Activity都一個Window對象的沧卢,Activity所有的View操作都托管給這個Window,我們可以把這個Window對象看成Activity的代理對象,包括ActivitysetContentViewfindViewById方法都是由Window接管的醉者。所以但狭,我們看到Activity的布局,實際上是Window的布局撬即。
??同時立磁,我們還知道,Android中的View成樹形結(jié)構(gòu)剥槐,樹必須就得有一個根唱歧,那么在Window中,這個View樹的根是什么呢粒竖?沒錯颅崩,就是我們DectorView。而DectorView本身是一個FrameLayout蕊苗,并沒有什么優(yōu)勢沿后?所以通常在DectorView里面還會有一個類似于LinearLayout,這個LinearLayout裝著兩部分的布局,一部分是ActionBar朽砰,另一部分是contentView尖滚,也就是我們通過setContentView方法設(shè)置的布局那部分,contentView的id固定是android.R.id.content锅移,這個在開發(fā)中有一定的幫助熔掺。
??而ViewRootImplperformTraversals方法就是DecorView的三大流程,然后借助DecorView將這三個流程傳遞下去,就像是事件分發(fā)機制一樣非剃,一層一層傳遞下去置逻。然后DecorView雖然是一個ViewGroup,但是它的三大流程跟普通的ViewGroup相比备绽,有一定的差別券坞。
??這里,我只是對Activity的布局基本介紹一下肺素,具體的原理和底層的代碼我也不是很了解恨锚,所以也不好深入的分析這一塊匿情,況且本文并不是分析這一塊的知識蟆炊,所以,這里我就簡單的說明一下∶睿現(xiàn)在我們來開始對源碼進行分析,先來看看performTraversals方法相關(guān)代碼:

            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;

                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

                    if (measureAgain) {
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
        }
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            performLayout(lp, mWidth, mHeight);
        }
        if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            performDraw();
        }
   }

??performTraversals方法比較長他挎,這里我只是將關(guān)鍵性代碼展示出來筝尾,在這里我們將知道三大流程的調(diào)用順序,最先是measure過程办桨,通過performMeasure方法開始的筹淫;其次,layout過程通過performLayout方法開始呢撞;最后损姜,draw過程通過performDraw方法開始的。接下來殊霞,我們簡單的看一下這三個方法摧阅。為什么簡單看一下呢?因為這三個方法就是操作分發(fā)到DecorView脓鹃,過程是非常的簡單逸尖。

(1). performMeasure

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

??performMeasure方法里面幾乎沒做什么,就是把Measure操作傳遞到DecorView里面瘸右,而這列mView就是DecorView對象。

(2). performLayout

??performLayout方法比較長岩齿,這里就不詳細的分析整個過程太颤,但是最終的結(jié)果就是調(diào)用了DecorViewlayout方法。待會我們在分析DecorView時盹沈,將會詳細的分析龄章。

(3). performDraw

??performDraw方法跟performLayout方法一樣,最后調(diào)用DecorViewdraw方法乞封,來繪制View做裙。具體的細節(jié),之后我們會詳細的分析肃晚。這里我們先有一個概念就行锚贱。

3. meaure

??三大流程相互獨立,如果合在一起分析難免會繞圈子关串,所以打算一一的來分析拧廊,將每個流程單獨的打通。首先我們來看看measure流程晋修。

(1).measure方法

??measure流程從ViewRootImplperformMeasure方法開始吧碾,調(diào)用了mView是什么呢?沒錯墓卦,就是DecorView倦春。DecorViewmeasure方法時從View那里繼承過來的。同時,不僅僅是DecorView睁本,所以控件的measure方法都是View那里繼承過來的山叮,因為measure是一個final方法,不能重寫添履。接下來屁倔,我們來看看Viewmeasure方法:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
            resolveRtlPropertiesIfNeeded();
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
        }
    }

??Viewmeasure方法比較簡單,為了代碼簡潔暮胧,我省略了很多沒必要的代碼锐借,我們只來看看核心代碼。整個measure方法流程往衷,我們只需要記住一點钞翔,就是判斷調(diào)用onMeasure方法,其他的代碼都是來幫助達到這個目的的席舍。
??我們來看看布轿,什么時候需要調(diào)用onMeasure方法,什么時候又不需要調(diào)用onMeasure方法来颤,而這種時候為什么不要調(diào)用onMeasure方法汰扭。這三個問題,是我們重點關(guān)心的福铅。
??從代碼中看來萝毛,我們知道forceLayout為true或者needsLayout為true時,有可能會調(diào)用onMeasure方法滑黔。而這兩個方法有表示什么意思呢笆包?
??forceLayout變量,我們從名字就知道是什么意思略荡,判斷時候強制布局庵佣,這個非常理解?那這個變量在什么時候為true呢汛兜?從View的Api方法中巴粪,我們找到了一個方法--forceLayout方法。

   public void forceLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
    }

??在forceLayout方法里面序无,這里mPrivateFlags變量跟 PFLAG_FORCE_LAYOUT做了一個或的位運算验毡,所以在measure方法里面,forceLayout才會為true:

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

??不過這里需要注意的是帝嗡,如果View第一次調(diào)用measure方法晶通,forceLayout是肯定為true的。具體是為什么哟玷,我也不太清楚狮辽,但是我們可以通過下面的代碼來驗證一下:

  public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    try {
      Field flags = this.getClass().getField("mPrivateFlags");
      flags.setAccessible(true);
      int anInt = flags.getInt(this);
      Log.i("pby123", " " + ((anInt & (0x00001000)) == (0x00001000)));
    } catch (NoSuchFieldException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }
  }

??下面就是log日志:


??所以我們可以得出一個結(jié)論一也,一個ViewonMeasure方法至少會被執(zhí)行一次。
??其實喉脖,我們可以這樣來想椰苟,如果在某些情況下onMeasure方法不會被執(zhí)行,那么我們在外部調(diào)用ViewgetMeasureWidth方法始終得到的是0树叽,這是不可能的舆蝴。同時,如果getMeasureWidth方法返回值為0的話题诵,那么在layout階段洁仗,我們根本不知道怎么進行布局,這也是不可能的性锭。這樣赠潦,我們就從側(cè)面可以得出,onMeasure方法至少會被執(zhí)行一次草冈。
??那為什么需要判斷是否執(zhí)行onMeasure方法呢她奥?這是為了避免多次執(zhí)行的onMeasure方法。
??另一個變量就是needsLayout怎棱,這個變量我們從名字上就可以判斷出來哩俭,表示是否需要布局,這個變量在什么時候為true呢蹄殃?

        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

??首先是判斷當前的寬高是否老的寬高相同携茂,如果相同,沒必要再次測量诅岩,同時如果當前ViewmodeEXACTLYmatch_parent都沒必要測量。為什么在EXACTLYmatch_parent時带膜,不要調(diào)用onMeasure測量呢吩谦?
??首先當modeEXACTLY時,表示當前View的寬高在第一次調(diào)用onMeasure方法已經(jīng)定死了膝藕,沒必要調(diào)用onMeasure方法進行測量式廷。
??其次就是match_parent,跟EXACTLY一樣芭挽,在父View分發(fā)measure事件下來時滑废,也是經(jīng)過第一次measure方法之后,寬高已經(jīng)定死了袜爪,后續(xù)就沒必要再次測量蠕趁。
??將measure方法簡單的分析一下之后,我們來看看DecorViewonMeasure方法辛馆,看看怎么測量自己和測量子View的俺陋。

(2).onMeasure方法

??DecorViewonMeasure方法比較長,我先簡單將這個方法分為過程,然后一一來分析腊状。

1.根據(jù)mode诱咏,來計算widthMeasureSpecheightMeasureSpec
2.如果存在outset,并且mode不為UNSPECIFIED,那么就會考慮到outset缴挖,重新計算widthMeasureSpecheightMeasureSpec
3.調(diào)用super.onMeasure方法袋狞,進行真正的測量。

??前兩步都沒有什么可以分析映屋,都是基本的操作苟鸯,相信熟悉Android測量規(guī)則的同學對此不會陌生。我們的重點在第三步里面秧荆。由于DecorView繼承于FrameLayout倔毙,所以,我們來看看FrameLayoutonMeasure方法乙濒。
??FrameLayoutonMeasure方法也比較長陕赃,這里先分為幾個過程。

  1. 調(diào)用每個child的measure方法颁股,測量每個child的寬高么库;并且記錄設(shè)置了match_parent屬性的child
  2. 調(diào)用setMeasuredDimension方法,對自身寬高進行設(shè)置甘有。
  3. 對設(shè)置了match_parent屬性的child進行測量诉儒。

??整個過程還是比較清晰,我們一個一個來分析亏掀。首先來看看第一個過程:


        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

??這個過程忱反,我們可以將它分成3個部分來看:

1 . 調(diào)用measureChildWithMargins方法對子View的進行測量。

  1. 不斷更新maxHeightmaxWidth的值滤愕,主要是用于父View的測量温算,如果父View本身為wrap_content,這兩個值就非常的重要。
  2. 記錄下設(shè)置match_parent屬性的child间影,當父View的寬高確定之后注竿,在進行第二次測量。

??2和3我們都不用看了魂贬,重點來看看measureChildWithMargins方法巩割。還記得在很久很久以前,我就分析過這個方法付燥,有興趣的同學可以去看看:Android 踩坑系列-ViewGroup的子View真正實現(xiàn)Margin屬性宣谈。好了,我們來看看measureChildWithMargins方法:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

??measureChildWithMargins方法比較簡單机蔗,就是通過調(diào)用getChildMeasureSpec方法來獲取child的MeasureSpec,然后將計算完畢的MeasureSpec傳遞到childmeasure進行真正測量蒲祈。這里的重點在getChildMeasureSpec方法甘萧,也是整個Android系統(tǒng)中的View測量核心之一,從這個方法里面梆掸,我們可以獲得很多的測量規(guī)則扬卷。我們重點分析分析:

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

??在分析這個方法之前,我們先對每個變量有一個認識酸钦。

變量名 類型 含義
spec int ViewMeasureSpec怪得,在getChildMeasureSpec方法里面,主要是通過這個變量來獲得父View的測量mode卑硫。因為子ViewMeasureSpec是由父ViewMeasureSpec和子ViewMeasureSpec共同決定的
padding int 主要是記錄父Viewpadding和子Viewmargin
childDimension int ViewMeasureSpec徒恋,與spec共同決定子ViewMeasureSpec

??整個getChildMeasureSpec方法比較簡單,分為三種大情況欢伏,每種大情況又分為三種小情況入挣,所以一共9種情況。現(xiàn)在我們通過一張表來分析硝拧。


??上面表中就詳細的分析了每種情況下規(guī)則径筏,這里我就不多說了。
??通過getChildMeasureSpec方法,我們可以獲得childMeasureSpec,然后調(diào)用childmeasure方法進行測量障陶,這就將measure事件分發(fā)下去了
??對第一個過程分析完畢之后滋恬,我們來看第二個過程:調(diào)用setMeasuredDimension方法,對自身寬高進行設(shè)置抱究。

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

??這一步比較簡單恢氯,通過resolveSizeAndState方法來獲得父ViewMeasureSpec。這里主要是考慮到父View可能是warp_content,所以有maxHeightmaxWidth參與鼓寺,這里就不分析resolveSizeAndState方法了勋拟,有興趣的同學可以看看。
??最后就是測量設(shè)置了match_parentchild,這個過程跟第一個過程比較像妈候,這里就在就不分析了指黎。
??整個measure過程,我們算是分析完畢了州丹。這里我做一個簡單的總結(jié)。

  1. measure過程從DecorViewmeasure方法開始,而measure本身不會進行測量杂彭,而是分發(fā)到了onMeasure方法墓毒。由于DecorView繼承于
    FrameLayout,所以調(diào)用的是FrameLayoutonMeasure方法亲怠。
  2. FrameLayoutonMeasure方法會測量自身所计,同時同時會將測量事件分發(fā)到每個View手里,從而完成了整個View樹的測量团秽。

??分析完畢measure過程主胧,現(xiàn)在我們來看看layout過程叭首。

4. layout

??前面已經(jīng)說了,ViewRootImpl會通過performLayout方法來分發(fā),而performLayout方法最終會調(diào)用DecorViewlayout方法進行布局踪栋。
??我們先來看看performLayout方法:

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;

        final View host = mView;
        if (host == null) {
            return;
        }

        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                // requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    // Set this flag to indicate that any further requests are happening during
                    // the second pass, which may result in posting those requests to the next
                    // frame instead
                    mHandlingLayoutInLayoutRequest = true;

                    // Process fresh layout requests, then measure and layout
                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        Log.w("View", "requestLayout() improperly called by " + view +
                                " during layout: running second layout pass");
                        view.requestLayout();
                    }
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;

                    // Check the valid requests again, this time without checking/clearing the
                    // layout flags, since requests happening during the second pass get noop'd
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // Post second-pass requests to the next frame
                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View", "requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame");
                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }

            }
        }
    }

??整個performLayout方法比較長焙格,我將它分為兩個部分。

  1. 如果host不為null,也就是DecorView不為null夷都,調(diào)用DecorViewlayout方法眷唉,將布局操作分發(fā)下去。
  2. 如果mLayoutRequesters不為空的話囤官,進行第二次布局冬阳。至于mLayoutRequesters什么不為空,這就涉及到requestLayout方法了党饮,后續(xù)我會單獨寫一篇文章來分析這個方法肝陪,本文不做過多的講解。

??這里刑顺,我們重點的看第一個部分氯窍。第一個部分調(diào)用了DecorViewlayout方法。而DecorViewlayout方法最終會調(diào)用到Viewlayout方法捏检,我們直接來看Viewlayout方法:

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

??Viewlayout方法也比較簡單荞驴,我將它分為兩個部分:

  1. 調(diào)用onLayout方法,進行真正的布局操作贯城。
  2. 回調(diào)OnLayoutChangeListeneronLayoutChange方法熊楼,告訴觀察者當前的布局已經(jīng)改變了。

??第二部分沒有分析的必要能犯,這個相信大多數(shù)的同學已經(jīng)司空見慣了鲫骗。我們重點來看看onLayout方法,而ViewonLayout方法本身是一個空方法踩晶。從這個空方法执泰,我們可以得出兩點結(jié)論:

  1. 普通的View調(diào)用layout方法進行布局,其實就是簡單將left渡蜻、top术吝、right、bottom4個變量記錄下來茸苇,并沒有做其他的操作布局排苍。
  2. ViewGroup必須實現(xiàn)onLayout方法,制定子View的布局規(guī)則学密。這就是ViewGroup有一個抽象方法的原因淘衙。

??既然Viewlayout調(diào)用了onLayout方法,接下來我們來看看DecorViewonLayout方法

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        getOutsets(mOutsets);
        if (mOutsets.left > 0) {
            offsetLeftAndRight(-mOutsets.left);
        }
        if (mOutsets.top > 0) {
            offsetTopAndBottom(-mOutsets.top);
        }
        if (mApplyFloatingVerticalInsets) {
            offsetTopAndBottom(mFloatingInsets.top);
        }
        if (mApplyFloatingHorizontalInsets) {
            offsetLeftAndRight(mFloatingInsets.left);
        }

        // If the application changed its SystemUI metrics, we might also have to adapt
        // our shadow elevation.
        updateElevation();
        mAllowUpdateElevation = true;

        if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
            getViewRootImpl().requestInvalidateRootRenderNode();
        }
    }

??DecorViewonLayout方法腻暮,我也簡單將它分為兩步:

  1. 調(diào)用super.onLayout方法彤守,也就是FrameLayoutonLayout方法來進行布局毯侦。
  2. 根據(jù)mOutsets來調(diào)整位置。至于mOutsets是什么具垫,抱歉侈离,我也不知道??。

??看來我們看看FrameLayoutonLayout方法做修。

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

??好嘛霍狰,又調(diào)用layoutChildren方法。在layoutChildren方法里面才是真正對child進行布局的操作饰及。
??這里就不對layoutChildren方法進行展開了蔗坯,因為比較簡單。就是根據(jù)每種ViewGroup不同的布局特性燎含,進行計算每個view的left宾濒、top泥从、right和bottom剑辫,然后調(diào)用childlayout方法可免。
??如果child是一個普通的View的話叫榕,那么調(diào)用layout方法就是記錄下4個值,等待draw流程的到來拐邪;如果child是一個ViewGroup的話睬塌,就會像FrameLayout一樣庐船,將layout事件分發(fā)下去颖御。
??如上榄棵,就是整個View的layout流程,這里我做一個簡單的總結(jié)潘拱。

  1. layout過程從DecorViewlayout方法(也是Viewlayout方法)開始疹鳄。在Viewlayout方法里面,會記錄下自身的left芦岂、top瘪弓、right、bottom4個屬性禽最,等待繪制腺怯,同時會調(diào)用onLayout方法將layout事件分發(fā)下去。
  2. 如果是普通的View川无,在layout方法里面調(diào)用onLayout方法是沒有用的,因為在View里面瓢喉,onLayout方法是一個空方法;如果是一個ViewGroup,在onLayout里面舀透,會調(diào)用每個childlayout方法。這樣整個layout流程就走通了决左。

??分析完layout流程之后愕够,我們再來看看三大流程的最后一個流程--draw走贪。

5. draw

??前面已經(jīng)說了,View樹的draw操作是從ViewRootImplperformDraw方法開始的』蟀牛現(xiàn)在我們來看看performDraw方法坠狡。

    private void performDraw() {
        // ······
        try {
            draw(fullRedrawNeeded);
        } finally {
        }
        // ······
    }

??performDraw方法比較長,這里我將代碼簡化了一下遂跟。說到底逃沿,performDraw方法就是調(diào)用draw方法。
??我們來看一下draw方法幻锁,整個draw方法比較長凯亮,我簡單的將它分為幾個部分:

  1. 根據(jù)fullRedrawNeeded變量,來計算dirty哄尔。dirty是一個矩陣假消,表示這次繪制的范圍。
  2. 調(diào)用drawSoftware方法進行繪制岭接。

??整個draw方法比較復(fù)雜富拗,因為這里面涉及到動畫之類的。如果此時在動畫鸣戴,表示本次繪制并不是最終的繪制啃沪,所以需要調(diào)用scheduleTraversals方法往主線程post一個Message用來下次繪制。
??其次窄锅,dirty的計算也是比較復(fù)雜的创千,我們這里也不去分析,因為這些都是計算酬滤,如果深入分析的話签餐,容易將我們聰明的大腦搞暈??。
??我們還是直接來看drawSoftware方法吧盯串。

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        final Canvas canvas;
        try {
            canvas = mSurface.lockCanvas(dirty);
            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            return false;
        }

        try {
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }

            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;

            try {
                canvas.translate(-xoff, -yoff);
                mView.draw(canvas);

            } finally {
            }
        } finally {
            try {
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                return false;
            }

        }
        return true;
    }

??整個drawSoftware方法比較長氯檐,我簡化了一下代碼。這里体捏,我先將整個方法分為3個部分:

  1. 根據(jù)dirty矩陣獲得繪制的Canvas對象
  2. 調(diào)用DecorViewdraw方法冠摄,繪制整個View
  3. 釋放Canvas

??我們一一的分析,首先來看看第一步几缭。

            canvas = mSurface.lockCanvas(dirty);
            // TODO: Do this in native
            canvas.setDensity(mDensity);

??這里通過mSurface來鎖定一塊畫布河泳,從而保證后續(xù)的繪制操作是線程安全的。
??與之對應(yīng)的是年栓,最后是釋放了這塊區(qū)域拆挥。
??我們重點的是是如下的代碼:

                mView.draw(canvas);

??上面的代碼最終是調(diào)用Viewdraw方法。我們來看看Viewdraw方法:

    public void draw(Canvas canvas) {
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);
            // Step 4, draw the children
            dispatchDraw(canvas);
            drawAutofilledHighlight(canvas);
            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }
            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);
            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);
            return;
        }
        //······
    }

??整個draw方法的流程非常的清晰,一個分為7步:

  1. 調(diào)用drawBackground方法纸兔,繪制背景惰瓜。
  2. 保存當前View的畫布層次,這一步只在繪制fading edge才會執(zhí)行汉矿。
  3. 調(diào)用onDraw方法崎坊,繪制View自身。
  4. 調(diào)用dispatchDraw方法洲拇,繪制children
  5. 繪制fading edge奈揍,這個只在View本身需要繪制ading edge才會執(zhí)行。
  6. 調(diào)用onDrawForeground方法赋续,繪制View的前景男翰。
  7. 調(diào)用drawDefaultFocusHighlight方法,繪制高亮部分蚕捉。

??View通過這7步就將整個View樹繪制完畢奏篙。這里,我們就不對每個過程做詳細的分析迫淹,因為每個過程都可以寫的非常多秘通,況且,我也不知道??敛熬。
??說到draw流程肺稀,就會想到invalidatepostInvalidate這兩個吊的一逼的方法,后續(xù)我會專門寫文章來分析這兩個方法应民,這里就不糾結(jié)了话原。
??draw流程算是分析完畢了,這里我對整個draw做一個小小的總結(jié)诲锹。

  1. draw流程是從ViewRootImplperformDraw方法開始繁仁,在這個方法主要是調(diào)用draw方法來進行操作。
  2. ViewRootImpldraw方法主要是做了兩步归园,一是計算畫布區(qū)域黄虱,用于后面獲取畫布對象;二是調(diào)用drawSoftware方法來進行操作庸诱。
  3. drawSoftware方法主要做了3步捻浦,一是獲得鎖定一個畫布對象;二是調(diào)用Viewdraw啟動整個draw流程的執(zhí)行桥爽;三是釋放畫布對象朱灿。
  4. Viewdraw方法一共分為7步。每步做了可以參考上面的說明钠四,這里就不重復(fù)的介紹了盗扒。對于Viewdraw方法,我們沒必要去沒比較去糾結(jié)每步是怎么做的,因為這樣容易導(dǎo)致深入源碼环疼,不可自拔习霹。

6. 總結(jié)

??View三大流程的流程到這里算是已經(jīng)結(jié)束,總的來說炫隶,介紹比較粗糙。但是我們分析源碼阎曹,沒必要去糾結(jié)每一行代碼伪阶,搞懂整個流程就OK,因為整個Android framework架構(gòu)是非常的復(fù)雜处嫌。
??這里我對三大流程做一個簡單的總結(jié)栅贴。

  1. 三大流程從View都是從ViewRootImplperformTraversals方法,分別調(diào)用performMeasure熏迹、performLayoutperformDraw方法進行三大流程的分發(fā)檐薯。
  2. 三大流程的執(zhí)行流程非常的相似,都是一種View樹的遞歸遍歷思想注暗。

??三大流程的源碼分析到此就結(jié)束了坛缕,接下來我會趁熱打鐵,進一步的分析requestLayout捆昏、invalidatepostInvalidate這三個方法赚楚。因為這三個方法跟layout和draw兩個流程有關(guān)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骗卜,一起剝皮案震驚了整個濱河市宠页,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寇仓,老刑警劉巖举户,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異遍烦,居然都是意外死亡俭嘁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門乳愉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兄淫,“玉大人,你說我怎么就攤上這事蔓姚〔端洌” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵坡脐,是天一觀的道長泄私。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么晌端? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任捅暴,我火速辦了婚禮,結(jié)果婚禮上咧纠,老公的妹妹穿的比我還像新娘蓬痒。我一直安慰自己,他們只是感情好漆羔,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布梧奢。 她就那樣靜靜地躺著,像睡著了一般演痒。 火紅的嫁衣襯著肌膚如雪亲轨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天鸟顺,我揣著相機與錄音惦蚊,去河邊找鬼。 笑死讯嫂,一個胖子當著我的面吹牛蹦锋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播端姚,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼晕粪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了渐裸?” 一聲冷哼從身側(cè)響起巫湘,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昏鹃,沒想到半個月后尚氛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡洞渤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年阅嘶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片载迄。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡讯柔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出护昧,到底是詐尸還是另有隱情魂迄,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布惋耙,位于F島的核電站捣炬,受9級特大地震影響熊昌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜湿酸,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一婿屹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧推溃,春花似錦昂利、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厢呵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間傀顾,已是汗流浹背襟铭。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留短曾,地道東北人寒砖。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像嫉拐,于是被迫代替她去往敵國和親哩都。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361