Android中View測(cè)量之MeasureSpec

當(dāng)View測(cè)量自身的大小的時(shí)候,會(huì)執(zhí)行measure(int widthMeasureSpec, int heightMeasureSpec)方法,至于measure方法內(nèi)容怎么執(zhí)行的,這里先不去探討。注意方法中兩個(gè)參數(shù),它們其實(shí)是一個(gè)int 類(lèi)型的MeasureSpec。MeasureSpec可以說(shuō)是View測(cè)量過(guò)程的前提,所以我們很有必要先來(lái)了解一下MeasureSpec。

MeasureSpec 工作原理

MeasureSpec 代表一個(gè)32位的int值,高2位代表SpecMode,低30位代表SpecSize。
SpecMode是指測(cè)量模式,SpecSize是指在某種測(cè)量模式下的大小。

MeasureSpec是View中的一個(gè)靜態(tài)內(nèi)部類(lèi)。

public static class MeasureSpec {

    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

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

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

}
  • 我們可以把MeasureSpec理解為測(cè)量規(guī)則,而這個(gè)測(cè)量規(guī)則是由測(cè)量模式和和該模式下的測(cè)量大小共同組成的。
int MeasureSpec = MeasureSpec.makeMeasureSpec(specSize,SpecMode);
  • 確定了View測(cè)量規(guī)則后,我們也可以通過(guò)測(cè)量規(guī)則獲取測(cè)量模式和該模式下的測(cè)量大小。
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

SpecMode有三類(lèi):

  1. UNSPECIFIED
    父容器不對(duì)View有任何限制,要多大給多大,這般情況一般用于系統(tǒng)內(nèi)部,表示一種測(cè)量狀態(tài),如ScrollView測(cè)量子View時(shí)用的就是這個(gè)。
  2. EXACTLY
    父容器已經(jīng)檢測(cè)出View所需要的大小,這個(gè)時(shí)候View的最終大小就是SpecSize所測(cè)定的值,它對(duì)應(yīng)于LayoutParams中的match_parent和具體的數(shù)值(如40dp,60dp)這兩種模式。
  3. AT_MOST
    父容器指定了一個(gè)可用大小即SpecSize,View的大小不能大于這個(gè)值,具體是什么值要看不同View的具體實(shí)現(xiàn)。它對(duì)應(yīng)于LayoutParams中的wrap_content.

普通View的MeasureSpec的創(chuàng)建過(guò)程

MeasureSpec很重要,上文中我們也了解了MeasureSpec的工作原理,那如何獲取MeasureSpec呢?下面就結(jié)合源碼來(lái)分析MeasureSpec的創(chuàng)建過(guò)程。

先來(lái)看下ViewGroup中的measureChild方法

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

在這個(gè)方法中,先獲取了子View的布局參數(shù),然后通過(guò)getChildMeasureSpec方法分別得到子View的寬高測(cè)量規(guī)則,即childWidthMeasureSpec和childHeightMeasureSpec,最后調(diào)用子View的measure方法,至此測(cè)量過(guò)程就由父View傳遞到了子View.。MeasureSpec確定后就可以在onMeasure方法確定View的測(cè)量寬高了。

我們重點(diǎn)分析的是getChildMeasureSpec方法,源碼如下:

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);  //返回父View的測(cè)量模式
        int specSize = MeasureSpec.getSize(spec);  //返回父View的測(cè)量大小

        int size = Math.max(0, specSize - padding);  //父View的測(cè)量大小 - 父View的padding占用的大小,剩余的即是子View可用的最大空間

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {  //子View大小為具體數(shù)值的情況
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {   //子View大小為match_parent的情況
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {   //子View大小為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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

此方法比較清晰,它主要用來(lái)通過(guò)父View的MeasureSpec和子View的LayoutParams來(lái)確定子View的MeasureSpec的,即普通View的MeasureSpec創(chuàng)建過(guò)程。

為了讓猿寶寶們更清晰的理解getChildMeasureSpec方法,借用《Android開(kāi)發(fā)藝術(shù)探索》中一張表格,如下:

MeasureSpec創(chuàng)建過(guò)程

DecorView的MeasureSpec創(chuàng)建過(guò)程

普通View的MeasureSpec的創(chuàng)建過(guò)程闡述了怎樣通過(guò)父View的MeasureSpec和子View的LayoutParams來(lái)確定子View的MeasureSpec。那頂級(jí)View,即DecorView的MeasureSpec創(chuàng)建過(guò)程又是怎樣的呢?ViewRootImp的measureHierarchy方法中有如下代碼:

    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

接著來(lái)看getRootMeasureSpec方法

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

從上述源碼,我們可以得出如下規(guī)則,具體根據(jù)它的LayoutParams來(lái)劃分:

  • LayoutParams.MATCH_PARENT:精確模式 其大小就為屏幕的尺寸大小
  • ViewGroup.LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超過(guò)屏幕的大小
  • 具體數(shù)值(如40dp):精確模式,大小為L(zhǎng)ayoutParamas指定的大小。

至此,相信大家對(duì)MeasureSpec有一定了解了,當(dāng)我們確定了View的MeasureSpec后,我們?cè)趺从盟鼇?lái)測(cè)量View的大小呢?具體請(qǐng)看View工作原理之measure過(guò)程解析。

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

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

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