View繪制流程及源碼解析(二)——onMeasure()流程分析

接著上一篇View繪制流程及源碼解析(一)——performTraversals()源碼分析,這一篇我們來具體看看三大流程的實(shí)現(xiàn)過程。

一. 從MeasureSpec說起

由于劇情的需要,我們不得不先說一下這個(gè)MeasureSpec势木。為什么要先說這個(gè)東西呢?再上一篇文章中,我們是否還記得三大流程正式開始的地方的代碼:

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

             // Ask host how big it wants to be
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            ......

可見离唬,getRootMeasureSpec()是獲取根布局的MeasureSpec,而最終的performMeasure()正式執(zhí)行measure()的流程就是所傳遞就去的參數(shù)就是這個(gè)getRootMeasureSpec()所計(jì)算得到的值,因此我們先要搞清楚這個(gè)MeasureSpec是什么玩意划鸽。
??那么什么是MeasureSpec呢输莺?首先戚哎,他代表的是一個(gè)32位的int值。其次模闲,它是View類中的一個(gè)內(nèi)部類建瘫,所以我們?cè)俳酉聛淼奈恼路治鲋校赡軙?huì)用到它所代表的int值它本身這兩種情況尸折,希望讀者再閱讀的時(shí)候可以加以區(qū)分啰脚。我們來看看它的源碼中的注釋(framewoks/base/core/java/android/view/View):

    /**
     * A MeasureSpec encapsulates the layout requirements passed from parent to child.
     * Each MeasureSpec represents a requirement for either the width or the height.
     * A MeasureSpec is comprised of a size and a mode. There are three possible
     * modes:
     * <dl>
     * <dt>UNSPECIFIED</dt>
     * <dd>
     * The parent has not imposed any constraint on the child. It can be whatever size
     * it wants.
     * </dd>
     *
     * <dt>EXACTLY</dt>
     * <dd>
     * The parent has determined an exact size for the child. The child is going to be
     * given those bounds regardless of how big it wants to be.
     * </dd>
     *
     * <dt>AT_MOST</dt>
     * <dd>
     * The child can be as large as it wants up to the specified size.
     * </dd>
     * </dl>
     *
     * MeasureSpecs are implemented as ints to reduce object allocation. This class
     * is provided to pack and unpack the <size, mode> tuple into the int.
     */
     public static class MeasureSpec {

上面的注釋交代了四點(diǎn):
(1)MeasureSpec封裝了從父布局傳遞給子布局的的布局要求
(2)每一個(gè)MeasureSpec代表了一個(gè)對(duì)寬度或著高度要求实夹。
(3)一個(gè)MeasureSpec由一個(gè)size一個(gè)mode組成,有三種可能:
??①UNSPECIFIED:父布局對(duì)于子布局/Veiw沒有任何約束橄浓,子布局可以是任何它想要的尺寸。根據(jù)主席的說法亮航,這中情況一般用于系統(tǒng)內(nèi)部反復(fù)測(cè)量控件大小荸实,我們?cè)儆玫倪^程中很少見到。
??②EXACTLY:
??父布局精確設(shè)定了子布局/View的大小缴淋,無論子布局想要多大的都將得到父布局給予的精確邊界准给。這種情況實(shí)際上就對(duì)應(yīng)的是我們?cè)趚ml文件或者LayoutParams中設(shè)置的xxdp/px或者MATH_PARENT這兩種情況,也就是精確的給予了布局邊界重抖。
??③AT_MOST:在確定的尺寸內(nèi)露氮,子布局/View可以盡可能的大。這個(gè)確定的尺寸钟沛,當(dāng)然指就值得就是父布局的大小畔规,畢竟子布局/View再大,也不能超出父布局的大小恨统。這種情況對(duì)應(yīng)的就是我們?cè)賦ml文件或者LayoutParams中設(shè)置的WRAP_CONTENT,子View可以隨著內(nèi)容的增加而申請(qǐng)更大的尺寸叁扫,但是不能超過父布局的大小。
(4)MeasureSpec將SpecMode和SpecSize打包成一個(gè)int值以便減少對(duì)象分配(的開支)畜埋。這個(gè)類提供了打包和解包size,mode的各中方法莫绣。

前面我們提到過,MeasureSpec是一個(gè)32位的int值悠鞍,通過上面一段我們又知道他是一個(gè)大小(Size)和模式(Mode)的組合值兔综,它的存在是為了減少對(duì)象分配的開支,那么我們可以做出進(jìn)一步解釋:
??①32位的int值中狞玛,高2位是SpecMode,代表測(cè)量模式软驰;低30位是SpecMode,代表再某種模式下得出的測(cè)量大小。
??②我們?cè)谏厦?1)(2)中我們將"要求"兩個(gè)字著重加黑心肪,就是為了強(qiáng)調(diào):這個(gè)MeasureSpec的值是"一個(gè)父?jìng)鬟f給子測(cè)量要求"锭亏,"怎么測(cè)量的要求",不是父對(duì)子強(qiáng)加的布局參數(shù)什么的,至于這個(gè)要求是什么硬鞍,怎么要求的慧瘤,后面我們會(huì)做進(jìn)一步說明戴已。
??③至于為什么要強(qiáng)調(diào)"傳遞"兩個(gè)字,主要是因?yàn)楣酰厦娴?測(cè)量要求"不是父強(qiáng)加給子的糖儡,而是兩個(gè)人一起商量著來的。具體的說怔匣,對(duì)于除了DecorView外的普通View握联,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的,當(dāng)然從源碼來看還與View的margin和padding有關(guān)每瞒;而對(duì)于DecorView來說金闽,其MeasureSpec由它自身的LayoutParams決定(畢竟它已經(jīng)是根布局了,上面再?zèng)]人了)剿骨。

說了這么一大通代芜,下面我們從源碼的角度帶大家分析一下根布局和自布局確定MeasureSpec的過程。

(一).根View/DecorView的MeasureSpec

前面開篇代碼中的getRootMeasureSpec()看名字我們也知道他是獲得根布局的MeasureSpec的方法
(framewoks/base/core/java/android/view/ViewRootImpl):

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

通過上述代碼浓利,我們可以看到RootView(DecorView)的MeasureSpec創(chuàng)建的全過程挤庇,分為三種情況:

  • LayoutParams.MATH_PARENT:精確模式,強(qiáng)制讓RootView的大小等于Window的大小。
  • LayoutParams.WRAP_CONTENT:最大模式贷掖,限制了RootView的最大窗口(父布局/View的大小罚随。
  • DEFAULT:指定大小,也就是在xml或者LayoutParams中指定View的寬度和高度的px/dp數(shù)羽资。

在這三種情況下,分別調(diào)用makeMeasureSpec()方法遵班,將模式和大小值打包成一個(gè)int值返回屠升。

(二).普通View的MeasureSpec

獲取普通View(除DecorView以外的View,大多數(shù)情況下指的是子布局/View)的MeasureSpec值的代碼在ViewGroup類中狭郑,相對(duì)復(fù)雜一點(diǎn)(framewoks/base/core/java/android/view/ViewGroup):

    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);
        //通過父View的MeasureSpec和子View的自己LayoutParams的計(jì)算腹暖,算出子View的MeasureSpec,然后父容器傳遞給
        //子容器的然后讓子View用這個(gè)MeasureSpec(一個(gè)測(cè)量要求翰萨,比如不能超過多大)去測(cè)量自己脏答,如果子View是
        //ViewGroup 那還會(huì)遞歸往下測(cè)量。
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

可以看到亩鬼,和開篇代碼中root測(cè)量的代碼一樣殖告,這里的Child在測(cè)量之前,先得getChildMeasureSpec()雳锋。還記得我們上面說的嗎黄绩?對(duì)于除了DecorView外的普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的玷过,當(dāng)然從源碼來看還與View的margin和padding有關(guān)爽丹,這里我們可以看源碼感受一下:

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//這里的mWidth就是DecorView的寬度筑煮,lp是它的LayoutParams參數(shù)。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);

可以看到粤蝎,getRootMeasureSpec中真仲,只需要傳遞進(jìn)去DecorView自身的寬度和它的LayoutParams寬度設(shè)置這兩個(gè)參數(shù);而getChildMeasureSpec中則需要考慮父布局的MeasureSpec(parentWidthMeasureSpec)父View的Padding初澎,自身設(shè)置的Margin秸应,已經(jīng)用掉的空間大小(widthUsed)LayoutParams的寬度設(shè)置(lp.width)谤狡。

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);   //父View的mode
        int specSize = MeasureSpec.getSize(spec);   //父View的size

        //(父View的大小-自己的Padding+子View的Margin)灸眼,得到值才是子View的大小,所以這里的size并不是子View的大小
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        //父View強(qiáng)加了一個(gè)精確的值給我們墓懂,父布局的模式是EXACTLY焰宣。這種情況對(duì)應(yīng)于MATH_PARENT和確定的px/dp值這兩種情況。
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                //子View的寬度和高度值為一個(gè)確定的dp/px值捕仔。
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                //子布局的LayoutParams為MATCH_PARENT匕积,那么最終得出的模式為EXACTLY。
                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.
                //子布局的LayoutParams設(shè)為WRAP_CONTENT榜跌,那么最終的布局模式為AT_MOST闪唆。
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        //父布局強(qiáng)加給我們一個(gè)最大值,即父布局的模式為AT_MOST.
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                //這個(gè)時(shí)候如果子布局/View想要一個(gè)精確的尺寸(xxdp/px)钓葫,那么最終的結(jié)果為EXACTLY悄蕾。
                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.
                //如果子View的LayoutParams為MATH_PARENT,那么最終得到的結(jié)果為AT_MOST础浮。
                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.
                //如果子View的LayoutParams為WRAP_CONTENT帆调,那么最終的結(jié)果模式為AT_MOST。
                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;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

我們將上面的這段代碼中子View的模式和父View模式的對(duì)應(yīng)關(guān)系用一個(gè)表格表示一下:

普通View的MeasureSpec創(chuàng)建規(guī)則.png

這里的resultMode為最終得到的子VIew的SpecMode豆同,resultSize為最終得到的子View的大小番刊。而childeSize就是上面的childDimension,即精確對(duì)定義子View大小的時(shí)候我們?cè)贚ayotParams中所設(shè)定的子View大小影锈,parentSize則為父容器中目前剩余的可用大小芹务。

二.measure()過程
(一).View的measure()過程

好了,清楚了MeasureSpec這個(gè)值以及我們?nèi)绾蔚玫礁竀iew及View的MeasureSpec之后呢鸭廷,我們接下來就開始看三大流程的第一步:Measure(測(cè)量)過程(framewoks/base/core/java/android/view/View):

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
    ......
        // measure ourselves, this should set the measured dimension flag back
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......

可以看到枣抱,這個(gè)Measure()方法是final的,也就是說我們不能重寫辆床,但是由于具體的測(cè)量過程是在onMeasure()中完成的沃但,所以有需要的話我們可以重寫這個(gè)方法:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    ......

顧名思義,這個(gè)setMeasuredDimension()方法是設(shè)置View寬/高的測(cè)量值佛吓,而它傳入的兩個(gè)參數(shù)getDefaultSize方法則是獲取到的寬/高值宵晚。

    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

首先我們來看看它的第一個(gè)參數(shù)getSuggestedMinimumWidth()垂攘,只有一句代碼,比較簡(jiǎn)單淤刃,他返回的是View的建議寬度/高度晒他。這個(gè)建議值是由View的背景尺寸和它的mMinWidth屬性決定的,mBackground就是背景逸贾,mMinWidth表示View的xml文件中android:minWidth屬性所指定的值陨仅,如果這個(gè)屬性沒有設(shè)置禽篱,那么就默認(rèn)為0究流。
??從源碼來看,這個(gè)方法中通過一個(gè)三目運(yùn)算符夺英,顯示判斷mBackground是否為null咪鲜,也就是有沒有狐赡。如果為ture也就是沒有背景,那么就返回mMinWidth的值疟丙,這個(gè)值可以為0颖侄;如果View指定了背景,那么返回的是mMinWidth和mBackground最小寬度兩個(gè)值中較大的那個(gè)享郊。這里指貼了getSuggestedMinimumWidth的源碼览祖,getSuggestedMinimumHeight()的源碼是一樣的。
??現(xiàn)在我們清楚了getDefaultSize()方法中第一個(gè)參數(shù)的意義炊琉,第二個(gè)參數(shù)measureSpec就是上面我們一直講的寬度/高度的MeasureSpec值≌沟伲現(xiàn)在我們來看看這個(gè)方法:

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

可以看出這個(gè)方法的邏輯實(shí)際上比較簡(jiǎn)單,如果是UNSPECIFIED這種情況苔咪,返回值是上面getSuggestedMinimumWidth()得到的值锰悼。對(duì)于我們來說,只需要關(guān)注AT_MOST和EXACTLY這兩種情況就行了悼泌。在這兩種情況中,返回值也就是View的測(cè)量值都等于 MeasureSpec.getSize(measureSpec):

        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

這個(gè)方法中做了什么呢?我們已經(jīng)知道夹界,這個(gè)measureSpec是一個(gè)封裝了View的SpecMode和SpecSize大小的int值馆里,其中高兩位是模式,低三十位是大小可柿。而這個(gè)MeasureSpec.getMode(measureSpec);MeasureSpec.getSize(measureSpec);實(shí)際上就是分別將傳遞進(jìn)去的measureSpec中封裝的SpecMode和SpecSize剝離出來鸠踪。
??對(duì)于DecorView/View,measureSpec就是我們?cè)陂_篇最上面的代碼中計(jì)算出來的getRootMeasureSpec(mWidth, lp.width)复斥。所以這里的specSize(對(duì)于DecorView)也就是我們最上邊代碼中的mWidth营密,也就是DecorView的Window的寬度。
??而對(duì)于其他的一些View的派生類目锭,如TextView评汰、Button纷捞、ImageView等,它們的onMeasure方法系統(tǒng)了都做了重寫被去,不會(huì)這么簡(jiǎn)單直接拿 MeasureSpec 的size來當(dāng)大小主儡,而去會(huì)先去測(cè)量字符或者圖片的高度等,然后拿到View本身content這個(gè)高度(字符高度等)惨缆,如果MeasureSpec是AT_MOST糜值,而且View本身content的高度不超出MeasureSpec的size,那么可以直接用View本身content的高度(字符高度等)坯墨,而不是像View.java 直接用MeasureSpec的size做為View的大小寂汇。
??還有一點(diǎn)需要說明的是,上面我們強(qiáng)調(diào)View測(cè)量值的原因是捣染,View最終值是在Layout階段確定的骄瓣,雖然幾乎是所有情況下View測(cè)量大小和最終大小是一樣的。

(二).ViewGroup的measure()過程

對(duì)于ViewGroup來說液斜,出了完成自己的測(cè)量過程外累贤,還會(huì)去遍歷調(diào)用所有子View/ViewGroup的measure()方法,各個(gè)子元素再去遞歸執(zhí)行這個(gè)方法少漆。和View不同的是臼膏,ViewGroup是一個(gè)抽象類,他并沒有重寫真正實(shí)現(xiàn)測(cè)量自身的onMeasure()方法,而是將該方法下發(fā)到了各個(gè)子類去實(shí)現(xiàn)示损。為什么ViewGroup不像View一樣對(duì)其onMeasure()方法做同意的實(shí)現(xiàn)呢渗磅?主要是因?yàn)椴煌腣iewGroup子類有不同的布局特性,比如LinearLayout和RelativeLayout這兩者的布局特性顯然不同检访,因此ViewGroup無法做統(tǒng)一的實(shí)現(xiàn)始鱼。
??雖然ViewGroup測(cè)量自身的過程下放到了各個(gè)子類中去實(shí)現(xiàn),但是在該類中我們?nèi)稳豢梢钥吹皆擃愡f歸調(diào)用子類測(cè)量方法的邏輯:

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

遍歷子Veiw脆贵,只要不是GONE的View都會(huì)調(diào)用医清。

    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

measureChild()方法中做的事情就是卖氨,取出子View的LayoutParams值,然后通過getChildMeasureSpec()方法(這個(gè)方法上面講過)得出子元素的MeasureSpec柏腻,然后直接調(diào)用子View的measure(),之后所做的事情就和"(一).View的measure()過程"中的過程一樣了五嫂。
??上面就是子View的測(cè)量邏輯,下面我們需要著重看一下ViewGroup()自身的測(cè)量過程沃缘,這里我們選擇LinearLayout來做分析(待會(huì)實(shí)例分析會(huì)用到)(framewroks/base/core/java/android/widget/linearlayout):

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

我們只分析豎直方向的測(cè)量過程躯枢,水平方向的過程是一樣的:

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;   //所有子View高度和
        int maxWidth = 0;   //所有子View中最大寬度值
        ......
        final int count = getVirtualChildCount();   //豎直方向的子View總數(shù)

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);    //獲取父布局的寬/高的模式
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;     //是否跳過測(cè)量
        ......

        int largestChildHeight = Integer.MIN_VALUE;

        // See how tall everyone is. Also remember max width.獲取每一個(gè)子View的高度闺金,同時(shí)記錄最大的寬度
        for (int i = 0; i < count; ++i) {   //循環(huán)遍歷每一個(gè)子View
            final View child = getVirtualChildAt(i);    //一個(gè)一個(gè)的獲取子View
            ......

            if (child.getVisibility() == View.GONE) {   //如果這個(gè)子View的可見性為GONE
               i += getChildrenSkipCount(child, i); //將這個(gè)子View連同他的序號(hào)一并計(jì)入getChildrenSkipCount()這個(gè)方法中
               continue;
            }

            if (hasDividerBeforeChildAt(i)) {   //如果這個(gè)子View帶有divider分割線
                mTotalLength += mDividerHeight; //mTotalLength累加分割線的高度。
            }//mDividerHeight對(duì)應(yīng)android:divider="@drawable/spacer_medium"這個(gè)屬性的高度讥巡,通常添加的是一個(gè)drawable圖片
            //獲取子View的LayoutParams屬性
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;   //totalWeight累加LayoutParams中的weight屬性值

            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                //如果這個(gè)子View高度的heightMode為EXACTLY槽棍,并且高度為0炼七,并且比重大于0布持。對(duì)照上文的表格可知按傅,當(dāng)父mode為EXACTLY
                //時(shí)胧卤,此時(shí)子View的高度為一個(gè)確定的值(這里為0),那么最終的高度就為childSize也就是0.顯然這個(gè)時(shí)候沒有可見的View况芒。
                final int totalLength = mTotalLength;
                //注意這里的totalLength/mTotalLength表示的是之前測(cè)量過的子View的高度和(以及如果本次測(cè)量的View有divider分
                //割線的話就加上本次測(cè)量View的分割線圖片高度)
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                //此時(shí)總高度(本次+測(cè)過的)就是totalLength和(totalLength+本次測(cè)量View的上下Margin)兩個(gè)值中較大的那個(gè).
                skippedMeasure = true;  //跳過測(cè)量的標(biāo)志位(畢竟這個(gè)高度是0绝骚,也就是沒有可見的View皮壁,就沒有測(cè)量的意義了)
            } else {
                int oldHeight = Integer.MIN_VALUE;  //oldHeight設(shè)為負(fù)無窮

                if (lp.height == 0 && lp.weight > 0) {
                    //else代表著此時(shí)父View的模式heightMode為UNSPECIFIED或者AT_MOST虑瀑,并且這個(gè)子View想延伸以填充剩余的可用
                    //空間(雖然它的高度為0,但是它的比重不為0扔水,說明他有填充剩余可用空間的“欲望”)魔市,此時(shí)我們將它的模式轉(zhuǎn)變成
                    //WRAP_CONTENT以便它最終的高度不為0(同樣對(duì)照上文的表格克制此時(shí)子View的大小為父View剩余空間的大小,相當(dāng)于
                    //match_parent,當(dāng)然這里暫時(shí)不考慮UNSPECIFIED這種情況待德。)
                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                //最終決定子View大小的方法
                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,    ①
                       totalWeight == 0 ? mTotalLength : 0);

                if (oldHeight != Integer.MIN_VALUE) {//如果oldHeight不為負(fù)無窮,說明經(jīng)歷了上段代碼中else的情況
                   lp.height = oldHeight;   //此時(shí)height=oldHeight=0
                }

                final int childHeight = child.getMeasuredHeight();  //獲取當(dāng)前子View的測(cè)量高度
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));
                       //mTotalLength + childHeight +lp.topMargin +lp.bottomMargin + getNextLocationOffset(child)
                       //表示之前測(cè)量過的子View的總高度+本次測(cè)量的子View的高度+本次測(cè)量的子Veiw的上下Margin,最后一個(gè)方法返
                       //回值為0较坛,可以直接忽略丑勤。
                       //那么此時(shí)總高度(之前的子View+本次測(cè)量的子View)就等于之前測(cè)量的子View的總高度和上面一串串值确封,兩者中的
                       //較大者爪喘,之所以這么干是因?yàn)閙argin+height的值可能是負(fù)的,不能因?yàn)閙argin的影響就縮小了總高度的測(cè)量和侦鹏,
                       //這樣做顯然得出的不是真實(shí)的高度.

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                    //這里的largestChildHeight表示高度最大的子Veiw
                }
            }
            ......

            final int margin = lp.leftMargin + lp.rightMargin;  //左右Margin
            final int measuredWidth = child.getMeasuredWidth() + margin;    //本次測(cè)量的子View的寬度+左右Magin
            maxWidth = Math.max(maxWidth, measuredWidth);   //獲取總的子View中最大的Veiw寬度
            ......
        }

上面這段代碼就是循環(huán)遍歷測(cè)量LinearLayout中所有子View的代碼略水,主要獲取兩個(gè)值:每個(gè)子View的高度所有子View中最大的寬度渊涝。其中高度和寬度均是加上上下/左右Margin之后的結(jié)果跨释。注釋已經(jīng)寫的很清楚了岁疼,這里不在鰲述捷绒。
??當(dāng)LinearLayout測(cè)量完了所有子View之后疙驾,就需要開始測(cè)量自身了:

        ......
        mTotalLength += mPaddingTop + mPaddingBottom;   //上面的for循環(huán)已經(jīng)計(jì)算完了所有的子Veiw的高度它碎,這里開始計(jì)算
                                            //LinearLayout自己的高度扳肛,也就是在所有子View高度之和的基礎(chǔ)之上加上上下Padding
        int heightSize = mTotalLength;

        // 將高度值再做一遍比較,即之前的高度值和getSuggestedMinimumHeight()較大的那個(gè)套腹,getSuggestedMinimumHeight()前面講過
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);         ②
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;   //最終得出的LinearLayout總高度
        ......(省略針對(duì)不同情況縮放子View的代碼)

        maxWidth += mPaddingLeft + mPaddingRight;   //LinearLayout總寬度

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());  //LinearLayout總寬度

        //所有的子View測(cè)量之后电禀,經(jīng)過一系類的計(jì)算之后通過setMeasuredDimension()設(shè)置自己的寬高尖飞,對(duì)于LinearLayout,可能是高度的
        //累加沮明,對(duì)于FrameLayout 可能用最大的字View的大小荐健,
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

當(dāng)子元素測(cè)量完之后摧扇,LinearLayout就會(huì)根據(jù)子元素的測(cè)量情況來測(cè)量自己的大小。針對(duì)豎直方向的LinearLayout在张,它的寬度測(cè)量實(shí)際上就是View的測(cè)量過程(最大的子View寬度+左右margin+左右padding)帮匾,具體到代碼中為:

    final int measuredWidth = child.getMeasuredWidth() + margin;
    maxWidth = Math.max(maxWidth, measuredWidth);
    maxWidth += mPaddingLeft + mPaddingRight;

而LinearLayout的高度測(cè)量稍微復(fù)雜一點(diǎn)瘟斜,具體來說是指,如果它的布局中高度采用的是math_parent或者具體的數(shù)值蛇尚,那么它的測(cè)量過程和View一致取劫,即高度為sizeSpec谱邪;如果它的布局中高度采用的是wrap_content,那么它的高度是所有子元素占用的高度總和虾标,但是仍然不能超過它父容器的剩余空間,當(dāng)然它的最終高度還是要考慮再豎直方向得到padding.(這段結(jié)論取自《Android開發(fā)藝術(shù)探索》,筆者在分析中并沒有找到支持該結(jié)論的相關(guān)源碼蘸吓,但是相信主席一回库继,如果有人找到了歡迎指教)
??最終決定它的高度的是上面②處的resolveSizeAndState()方法(framewoks/base/core/java/android/view/View):

    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

這個(gè)方法主要的作用是在父布局的MeasureSpec約束下艺谆,協(xié)調(diào)所需要的大小和狀態(tài)静汤。其中size是當(dāng)前LinearLayout所需要的寬度(最大寬度),measureSpec是父布局的MeasureSpec抹估,childMeasuredState為子View的測(cè)量狀態(tài)药蜻。上面的代碼已經(jīng)很清楚了谷暮,這里不再加以分析。
這里還要說明的一點(diǎn)就是上段代碼中①處的measureChildBeforeLayout()方法:

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }
    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);
    }

可以看到,改方法最終調(diào)用了ViewGroup類中的measureChildWithMargins()方法班利,最終調(diào)用了View類中的measure()方法罗标。

(三)舉例說明

進(jìn)過上面的過程我們的measure過程的源碼就分析完了闯割,下面我們通過一個(gè)例子來具體說明下這個(gè)過程。首先呢谢澈,我們要回顧一下淺談Activity從建立到顯示(setContentView源碼淺析)中的一些內(nèi)容:這片文章中锥忿,我們
為了說明開發(fā)中我們自己寫的xml文件在DecorView的層次關(guān)系淹朋,用到了一個(gè)系統(tǒng)定義的xml文件:R.layout.screen_simple,這個(gè)布局是我們的theme設(shè)置為NoTitleBar時(shí)的布局(SDK/platforms/android-23/data/res/layout/screen_simple.xml):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

這個(gè)時(shí)候我們定義了一個(gè)activity_main.xml文件:

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/linearLayout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_marginTop="50dp"
       android:paddingBottom="50dp"
       android:orientation="vertical">
       <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Hellow word" 
            android:textSize="20sp"/>
       <View
            android:id="@+id/selfView"
            android:layout_width="match_parent"
            android:layout_height="100dp" />
</LinearLayout>

并且畫出了兩者的層級(jí)關(guān)系圖:

screen_simple.xml中的關(guān)系.png

OK,相信通過上一篇文章的分析你已經(jīng)清楚上面這張途中各部分之間的關(guān)系了春感,下面我們對(duì)上面布局中的一些元素做一下必要的說明:
??①上面我們說過鲫懒,這個(gè)R.layout.screen_simple是我們將系統(tǒng)的theme設(shè)置為NoTitleBar時(shí)的布局甲献。NoTitleBar也就意味著沒有titlebar或者actionbar的干擾晃洒,整個(gè)content區(qū)域只有我們的activity_main.xml,這樣就方便了我們之后對(duì)于測(cè)量流程的分析球及。
??②圖中id為statusBarBackgroud的Veiw,顧名思義就是狀態(tài)欄背景镊尺。為了抓住主要矛盾鹅心,在之后的分析中我們會(huì)跳過狀態(tài)欄的測(cè)量分析颅筋。
??③screen_simple下屬的ViewStub,我們對(duì)這個(gè)東西做一下說明:ViewStub是View的子類议泵;它不可見(可見性為GONE),寬高均為0先口;它用來延遲加載布局資源(惰性加載),常用于布局優(yōu)化谐宙。顯然凡蜻,ViewStub在這里是用來惰性加載titlebar/actionbar的,關(guān)于什么是惰性加載忠荞,讀者可以自行搜索钻洒;另外,由于它的可見性為GONE,并且寬高都是0头遭,因此再measure()的過程中系統(tǒng)也會(huì)自動(dòng)跳過计维。

OK,下面我們正式開始測(cè)量流程的分析。首先欠母,我們要回到三大流程開始的地方踩寇,也就是ViewRootImpl類中的performTraversals()方法俺孙,不清楚的同學(xué)可以看View繪制流程及源碼解析(一)——performTraversals()源碼分析這片文章睛榄。再這片文章的"第六段代碼"中,我們看到了measure()過程的開始方法——performMeasure():

    ......
    //mStopped的注釋:Set to true if the owner of this window is in the stopped state.如果此窗口的所有者
    //(Activity)處于停止?fàn)顟B(tài)票罐,則為ture.
    if (!mStopped || mReportNextDraw) {        //mReportNextDraw Window上報(bào)下一次繪制.
        boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
        if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
            //mWidth != host.getMeasuredWidth() 表示frame的寬不等于初始DecorView寬.
            //getMeasuredWidth()方法可以獲取View測(cè)量后的寬高该押,host上面說過為DecorView根布局.

            //獲得view寬高的測(cè)量規(guī)格烟具,lp.width和lp.height表示DecorView根布局寬和高
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

            // Ask host how big it wants to be
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//開始執(zhí)行測(cè)量操作  ①

第一步,DecorVeiw的測(cè)量
??通過上篇文章之前的分析可知,這里的getRootMeasureSpec()就是獲取DecorView的MeasureSpec,也就是說這里進(jìn)行的是頂層View——DecorView的測(cè)量。在performMeasure()方法中冀痕,我們調(diào)用了DecorView的measure()方法:

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);   //mView就是DecorView
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

而DecorView是一個(gè)FrameLayout,因此我們需要區(qū)看FrameLayout類的onMeaure()方法,這個(gè)方法相對(duì)LinearLayout的簡(jiǎn)單多了(早知道在上面我就直接分析FrameLayout了,mdzz):

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        .......

        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) {
            // 循環(huán)遍歷的子View,只要不是GONE的都會(huì)參與測(cè)量
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ......
            }
        }
        ......
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
        ......
    }

需要說明的一點(diǎn)是婿斥,由于DecorView是最頂層的View,因此它的寬高M(jìn)easureSpec是由它自己的寬度和LayoutParams決定getRootMeasureSpec(mWidth, lp.width);getRootMeasureSpec(mHeight, lp.height);携龟。再者,由于DecorView是最頂層View的緣故蕊蝗,通常情況下它可以代表整個(gè)屏幕蓬戚,因此mWidth和mHeight既是DecorView的寬高也是整個(gè)屏幕的寬高。lp是WindowManager.LayoutParams幢泼,它的lp.width和lp.height的默認(rèn)值是MATCH_PARENT,所以對(duì)于DecorView通過getRootMeasureSpec 生成的測(cè)量規(guī)格MeasureSpec 的mode是MATCH_PARENT,size是屏幕的高寬招驴,這里我們假設(shè)有一個(gè)1920x1080像素的手機(jī)那么size就對(duì)應(yīng)這兩個(gè)值别厘。再根據(jù)文章最開頭第一大點(diǎn)中的第(一)小點(diǎn)"根布局的MeasureSpec獲取"可知拥诡,當(dāng)根布局的LayoutParams為Match_parent時(shí)触趴,它的MeasureSpec.mode為EXACTLY,用圖像表示為:

DecorView的MeasureSpec.png

第二步袋倔,DecorView到screen_simple
??在FrmeLayout的onMeasure()方法中雕蔽,for循環(huán)遍歷的子View只有兩個(gè):screen_simple,xml和stautsbarbackdroud,前面我們已經(jīng)說過會(huì)跳過狀態(tài)欄的分析,所以這里只看screen_simple,xml對(duì)應(yīng)的View批狐。measureChildWithMargins()中第一個(gè)參數(shù)child代表的就是screen_simple.xml對(duì)應(yīng)的View,可以看到最終調(diào)用了該View的measure()方法,并傳遞進(jìn)去了DecorView的寬高M(jìn)easureSpec嚣艇。
??我們看看此時(shí)screen_simple對(duì)應(yīng)的View的MeasureSpec的兩個(gè)參數(shù)承冰。由于該View是系統(tǒng)的View,它的LayoutParams默認(rèn)都是match_parent食零,又因?yàn)樗母覆季?DecorVeiw)的mode為EXACTLY,根據(jù)我們文章開頭LayoutParams和MeasureSpec的對(duì)應(yīng)關(guān)系可知困乒,screen_simple的size為父布局剩余的空間大小,mode為EXACTLY贰谣;又因?yàn)镈ecorView是FrameLayout,statusbar是疊在screen_simple上面的娜搂,因此相當(dāng)于狀態(tài)欄不占用底下一層的DecorView的空間——綜上,screen_simple的size等于DecorView的size為1920x1080,mode為EXACTLY吱抚。用圖表示:

screen_simple的MeasureSpec.png

算出screen_simple.xml對(duì)應(yīng)的View的MeasureSpec之后呢百宇,就開始計(jì)算調(diào)用measure()方法計(jì)算它的大小。由上面層次圖及xml文件可知screen_simple.xml對(duì)應(yīng)的View是一個(gè)方向?yàn)関ertical的LinearLayout秘豹,因此child.measure(childWidthMeasureSpec, childHeightMeasureSpec);最終會(huì)調(diào)用LinearLayout的onMeasure()方法携御,(這個(gè)過程在上文是有詳細(xì)的講解的,如果不記得的話可以翻上去看一下)最終會(huì)調(diào)用LinearLayout的measureChildBeforeLayout()并在該方法中又一次調(diào)用measureChildWithMargins(child, widthMeasureSpec, totalWidth,heightMeasureSpec, totalHeight);方法既绕,開始計(jì)算screen_simple.xml當(dāng)中各個(gè)子元素的大小(子元素測(cè)量完之后再測(cè)量自身的大小)啄刹。
??這里還要注意的一點(diǎn)是screen_simple.xml對(duì)應(yīng)的View有一個(gè)padding值:mPaddingTop=100px,這個(gè)可能和狀態(tài)欄的高度有關(guān)凄贩,我們測(cè)量的最后會(huì)發(fā)現(xiàn)id/statusBarBackground的View的高度剛好等于100px誓军。
第三步,從screen_simple到R.id.content
??這個(gè)時(shí)候我們進(jìn)入了screen_simple.xml對(duì)應(yīng)的View中怎炊,該View有兩個(gè)子元素:R.id.content和VeiwStub谭企。ViewStub上面我們已經(jīng)說過廓译,它的可見性為GONE评肆,且寬高均為0,所以測(cè)量的話會(huì)系統(tǒng)會(huì)直接跳過非区。那么我們就只剩下R.id.content了,通過上面的結(jié)構(gòu)層次圖瓜挽,我們知道這個(gè)R.id.content實(shí)際上就是我們寫的activity_main.xml文件。
??這里一樣征绸,我們首先需要根據(jù)screen_simple.xml對(duì)應(yīng)View的MeasureSpec(size:1980x1080,mode:EXACTLY)和R.id.content的LayoutParams(match_parent x match_parent)來計(jì)算出它的MeasureSpec,當(dāng)然這里的計(jì)算也需要遵循ViewGroup的measureChildWithMargins()方法久橙,這個(gè)方法我們之前講過,這里我們?cè)儋N一遍源碼:

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

這里我們只看高度管怠,之前提到過淆衷,screen_simple.xml對(duì)應(yīng)的View有一個(gè)padding值:mPaddingBottom=100px,因此getChildMeasureSpec(int spec, int padding, int childDimension)中第二個(gè)參數(shù)的值為100px渤弛,第三個(gè)參數(shù)值為match_parent祝拯。由int size = Math.max(0, specSize - padding);可知這里的size變量為(1920-100)x1080 = 1820x1080,又父布局screen_simple.xml的specMode為EXACTLY,childDimension == LayoutParams.MATCH_PARENT,因此最終的結(jié)果resultSize = size = 1820x1080佳头;resultMode = MeasureSpec.EXACTLY鹰贵。用圖表示為:

R.id.content的MeasureSpec.png

第四步,activity_main.xml測(cè)量規(guī)格MeasureSpec分析
??R.id.content是一個(gè)FrameLayout康嘉,下面就一個(gè)activity_main.xml子元素碉输,這個(gè)時(shí)候我們同樣先要確定activity_main.xml這個(gè)LinearLayout(id/linearLayout)的MeasureSpec值,這里我們?cè)儋N一下布局:

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/linearLayout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_marginTop="50dp"
       android:paddingBottom="50dp"
       android:orientation="vertical">
       <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Hellow word" />
       <View
            android:id="@+id/selfView"
            android:layout_width="match_parent"
            android:layout_height="100dp" />
</LinearLayout>

注意id/linearLayout的android:layout_width亭珍,android:layout_height敷钾,這幾個(gè)參數(shù)的值。父布局screen_simple.xml的MeasureSpec.heightMode = EXACTLY,id/linearLayout的android:layout_height為wrap_content肄梨,對(duì)照上文的LayoutParams與MeasureSpec可知闰非,id/linearLayout的MeasureSpec.heightMode為AT_MOST;同理峭范,它的MeasureSpec.weightMode為EXACTLY财松;
??下面我們來看它的specSize,注意:android:layout_marginTop="50dp"纱控,這里我們需要說明一下Android設(shè)備中px/dp的換算——dp x 基準(zhǔn)比例 = px辆毡; 基準(zhǔn)比例 = 系統(tǒng) DPI / 160 ;一般在1920*1080 分辨率的手機(jī)上 默認(rèn)就使用 480 的 dpi ,不管的你的尺寸是多大都是這樣甜害,除非廠家手動(dòng)修改了配置文件舶掖,因此這里可以得到——基準(zhǔn)比例=480 / 160 = 3;px = dp x 基準(zhǔn)比例 = 50 x 3 = 150px尔店。(關(guān)于更多這方面的知識(shí)推薦一片文章[Android] Android開發(fā)中dip眨攘,dpi,density嚣州,px等詳解感興趣的瀆職可自行參閱)鲫售。也就是說,id/linearLayout在高度上少了150px该肴,根據(jù)上面getChildMeasureSpec()方法中第二個(gè)參數(shù):padding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed可知padding = 150px,又int size = Math.max(0, specSize - padding) = 1670 x 1080情竹。
??所以我們現(xiàn)在可以得出id/linearLayout的MeasureSpec,用圖表示:

![Uploading TextView的MeasureSpec_037776.png . . .]

第五步匀哄,TextView和View的測(cè)量分析
1.TextView
??接上面秦效,先看TextView:

<TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Hellow word" />

由它的父布局id/linearLayout的MeasureSpec.mode可知,TextView的MeasureSpec.heightMode為AT_MOST涎嚼;MeasureSpec.weightMode為EXACTLY阱州;至于它的大小,由于id/linearLayout中有android:paddingBottom="50dp",它的子元素的生存空間又被壓縮了50dp也就是150px法梯,所以TextView的Measure.specSize為(1670-150)x1080=1520x1080(注意這個(gè)是對(duì)TextView的寬高約大小束苔货,可不是正真的大小,不要忘了MeasureSpecd的正真含義)。算出id/textView 的MeasureSpec 后蒲赂,接下來我們來計(jì)算textView的正真大小:textView.measure(childWidthMeasureSpec, childHeightMeasureSpec);跳轉(zhuǎn)到TextView.onMeasure()方法中(frameworks/base/core/java/android/widget/TextView):

    if (heightMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            height = heightSize;
            mDesiredHeightAtMeasure = -1;
        } else {
            int desired = getDesiredHeight();   //desired = 107px

            height = desired;
            mDesiredHeightAtMeasure = desired;

            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(desired, heightSize); //heightSize = 1520px
            }
        }

TextView字符的高度(也就是TextView的content高度[wrap_content])測(cè)出來等于107px阱冶,107px 并沒有超過1980px(允許的最大高度),所以實(shí)際測(cè)量出來TextView的高度是107px滥嘴。(這個(gè)筆者并沒有親自測(cè)木蹬,直接拿來主義,但是差距不會(huì)太大)
??最終算出id/text 的mMeasureWidth=1080,mMeasureHeight=107px若皱。

TextView的MeasureSpec.png

2.View
??接下來我們來看這個(gè)id/selfView的子View:

<View
    android:id="@+id/selfView"
    android:layout_width="match_parent"
    android:layout_height="100dp" />

由于這里指明了它的高度為100dp也就是300px镊叁,加上它的父布局id/linearLayout的MeasureSpec.heightMode為AT_MOST,因此它的MeasureSpec.heightMode為EXACTLY,MeasureSpec.weightMode也為EXACTLY走触,heightSpecSize為自己的尺寸300px,所以它的specSize為300 x 1080晦譬,然后計(jì)算View的大小:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

最終算出id/selfView的mMeasureWidth=1080px,mMeasureHeight=300px。

id.selfView的MeasureSpec.png

第六步互广,activity_main.xml自身大小測(cè)量分析
??id/linearLayout 的子View的高度都計(jì)算完畢了敛腌,接下來id/linearLayout就通過所有子View的測(cè)量結(jié)果計(jì)算自己的高寬,之前我們著重分析過LinearLayout的測(cè)量過程惫皱,這里我們?cè)儋N一下關(guān)鍵代碼:

mTotalLength += mPaddingTop + mPaddingBottom;

簡(jiǎn)單理解就是子View的高度的累積+自己的Padding,也就是107px(TextView) + 300px(View) + 150px(paddingBottom="50dp") = 557px最終算出id/linearLayout的mMeasureWidth=1080px,mMeasureHeight=557px像樊。

第七步,從activity到R.id.content旅敷,計(jì)算R.id.content自身的大小
第八步生棍,從R.id.content到screen_simple,計(jì)算screen_simple自身的大小
第九步媳谁,從screen_simple到DecotView,計(jì)算DeorView自身的大小
后面三步和第六步同理涂滴,邏輯都是計(jì)算完了子元素的大小加上自身的padding,計(jì)算出自身的大小晴音。

站在巨人的肩膀上摘蘋果:
Android View的繪制流程
②《Android開發(fā)藝術(shù)探索》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柔纵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子段多,更是在濱河造成了極大的恐慌首量,老刑警劉巖壮吩,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件进苍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鸭叙,警方通過查閱死者的電腦和手機(jī)觉啊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沈贝,“玉大人杠人,你說我怎么就攤上這事。” “怎么了嗡善?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵辑莫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我罩引,道長(zhǎng)各吨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任袁铐,我火速辦了婚禮揭蜒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剔桨。我一直安慰自己屉更,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布洒缀。 她就那樣靜靜地躺著瑰谜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪树绩。 梳的紋絲不亂的頭發(fā)上似舵,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音葱峡,去河邊找鬼砚哗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛砰奕,可吹牛的內(nèi)容都是我干的蛛芥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼军援,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼仅淑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胸哥,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤涯竟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后空厌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庐船,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年嘲更,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筐钟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赋朦,死狀恐怖篓冲,靈堂內(nèi)的尸體忽然破棺而出李破,到底是詐尸還是另有隱情,我是刑警寧澤壹将,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布嗤攻,位于F島的核電站,受9級(jí)特大地震影響诽俯,放射性物質(zhì)發(fā)生泄漏屯曹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一惊畏、第九天 我趴在偏房一處隱蔽的房頂上張望恶耽。 院中可真熱鬧,春花似錦颜启、人聲如沸偷俭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涌萤。三九已至,卻和暖如春口猜,著一層夾襖步出監(jiān)牢的瞬間负溪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工济炎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留川抡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓须尚,卻偏偏與公主長(zhǎng)得像崖堤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耐床,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容