Android視圖繪制流程之onMeasure()

onMeasure()

用于測(cè)量視圖的大小
View系統(tǒng)的繪制流程會(huì)從ViewRootperformTraversals()方法中開(kāi)始
在其內(nèi)部調(diào)用View的measure()方法。

measure()方法接收兩個(gè)參數(shù)

  • widthMeasureSpec用于確定視圖的寬度的規(guī)格和大小
  • heightMeasureSpec用于確定視圖的高度的規(guī)格和大小

MeasureSpec的值由specSize和specMode共同組成的
其中specSize記錄的是大小,specMode記錄的是規(guī)格。

specMode一共有三種類(lèi)型:
EXACTLY
表示父視圖希望子視圖的大小應(yīng)該是由specSize的值來(lái)決定的,系統(tǒng)默認(rèn)會(huì)按照這個(gè)規(guī)則來(lái)設(shè)置子視圖的大小,開(kāi)發(fā)人員當(dāng)然也可以按照自己的意愿設(shè)置成任意的大小。
AT_MOST
表示子視圖最多只能是specSize中指定的大小,開(kāi)發(fā)人員應(yīng)該盡可能小得去設(shè)置這個(gè)視圖,并且保證不會(huì)超過(guò)specSize。系統(tǒng)默認(rèn)會(huì)按照這個(gè)規(guī)則來(lái)設(shè)置子視圖的大小,開(kāi)發(fā)人員當(dāng)然也可以按照自己的意愿設(shè)置成任意的大小。
UNSPECIFIED
表示開(kāi)發(fā)人員可以將視圖按照自己的意愿設(shè)置成任意的大小,沒(méi)有任何限制。這種情況比較少見(jiàn),不太會(huì)用到。

那么widthMeasureSpec和heightMeasureSpec這兩個(gè)值又是從哪里得到的呢?

通常情況下,這兩個(gè)值都是由父視圖經(jīng)過(guò)計(jì)算后傳遞給子視圖的,說(shuō)明父視圖會(huì)在一定程度上決定子視圖的大小。

但是最外層的根視圖,它的widthMeasureSpec和heightMeasureSpec又是從哪里得到的呢?

這就需要去分析ViewRoot中的源碼了,觀察performTraversals()方法可以發(fā)現(xiàn)如下代碼:

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

這里調(diào)用了getRootMeasureSpec() 方法去獲取widthMeasureSpec和heightMeasureSpec的值
注意方法中傳入的參數(shù),其中lp.widthlp.height在創(chuàng)建ViewGroup實(shí)例的時(shí)候就被賦值了,它們都等于MATCH_PARENT。

然后看下getRootMeasureSpec() 方法中的代碼,如下所示:

private int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

可以看到,這里使用了MeasureSpec.makeMeasureSpec() 方法來(lái)組裝一個(gè)MeasureSpec

  • 當(dāng)rootDimension 參數(shù)等于MATCH_PARENT的時(shí)候,MeasureSpec的specMode就等于EXACTLY
  • 當(dāng)rootDimension 等于WRAP_CONTENT的時(shí)候,MeasureSpec的specMode就等于AT_MOST。

并且MATCH_PARENT和WRAP_CONTENT時(shí)的specSize都是等于windowSize的,也就意味著根視圖總是會(huì)充滿(mǎn)全屏的。

介紹了這么多MeasureSpec相關(guān)的內(nèi)容,接下來(lái)我們看下View的measure() 方法里面的代碼吧,如下所示:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {
        mPrivateFlags &= ~MEASURED_DIMENSION_SET;
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
        }
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }
        mPrivateFlags |= LAYOUT_REQUIRED;
    }
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
}

注意觀察,measure()這個(gè)方法是final的,因此我們無(wú)法在子類(lèi)中去重寫(xiě)這個(gè)方法,說(shuō)明Android是不允許我們改變View的measure框架的。
然后在代碼里調(diào)用了onMeasure() 方法,這里才是真正去測(cè)量并設(shè)置View大小的地方,默認(rèn)會(huì)調(diào)用getDefaultSize()方法來(lái)獲取視圖的大小,如下所示:

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

這里傳入的measureSpec是一直從measure()方法中傳遞過(guò)來(lái)的。
然后調(diào)用MeasureSpec.getMode() 方法可以解析出specMode
調(diào)用MeasureSpec.getSize() 方法可以解析出specSize。
接下來(lái)進(jìn)行判斷,如果specMode等于AT_MOST或EXACTLY就返回specSize,這也是系統(tǒng)默認(rèn)的行為。
之后會(huì)在onMeasure()方法中調(diào)用setMeasuredDimension()方法來(lái)設(shè)定測(cè)量出的大小,這樣一次measure過(guò)程就結(jié)束了。

當(dāng)然,一個(gè)界面的展示可能會(huì)涉及到很多次的measure,因?yàn)橐粋€(gè)布局中一般都會(huì)包含多個(gè)子視圖,每個(gè)視圖都需要經(jīng)歷一次measure過(guò)程。ViewGroup中定義了一個(gè)measureChildren() 方法來(lái)去測(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);
        }
    }
}

這里首先會(huì)去遍歷當(dāng)前布局下的所有子視圖,然后逐個(gè)調(diào)用measureChild() 方法來(lái)測(cè)量相應(yīng)子視圖的大小,如下所示:

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

可以看到,分別調(diào)用了getChildMeasureSpec()方法來(lái)去計(jì)算子視圖的MeasureSpec,計(jì)算的依據(jù)就是布局文件中定義的MATCH_PARENT、WRAP_CONTENT等值,這個(gè)方法的內(nèi)部細(xì)節(jié)就不再貼出。
然后調(diào)用子視圖的measure()方法,并把計(jì)算出的MeasureSpec傳遞進(jìn)去,之后的流程就和前面所介紹的一樣了。

當(dāng)然,onMeasure()方法是可以重寫(xiě)的,也就是說(shuō),如果你不想使用系統(tǒng)默認(rèn)的測(cè)量方式,可以按照自己的意愿進(jìn)行定制,比如:

public class MyView extends View {
 
    ......
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(200, 200);
    }
 
}

這樣的話(huà)就把View默認(rèn)的測(cè)量流程覆蓋掉了,不管在布局文件中定義MyView這個(gè)視圖的大小是多少,最終在界面上顯示的大小都將會(huì)是200*200。

需要注意的是,在setMeasuredDimension()方法調(diào)用之后,我們才能使用getMeasuredWidth()和getMeasuredHeight()來(lái)獲取視圖測(cè)量出的寬高,以此之前調(diào)用這兩個(gè)方法得到的值都會(huì)是0。

由此可見(jiàn),視圖大小的控制是由父視圖、布局文件、以及視圖本身共同完成的,父視圖會(huì)提供給子視圖參考的大小,而開(kāi)發(fā)人員可以在XML文件中指定視圖的大小,然后視圖本身會(huì)對(duì)最終的大小進(jìn)行拍板。

到此為止,我們就把視圖繪制流程的第一階段分析完了。

Home:返回首頁(yè)

Next:onLayout()


作者:guolin
來(lái)源:CSDN
原文:https://blog.csdn.net/guolin_blog/article/details/16330267
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!

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

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