onMeasure()
用于測(cè)量視圖的大小
View系統(tǒng)的繪制流程會(huì)從ViewRoot的performTraversals()方法中開(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.width和lp.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)附上博文鏈接!