簡(jiǎn)介
組件在頁面上的展示要經(jīng)過測(cè)量、布局晴股、繪制三個(gè)過程城须,而測(cè)量就是把我們定義的組件寬和高轉(zhuǎn)換為具體的大小的這個(gè)過程。
我們常見的組件的寬高定義如下:
<TextView
android:id="@+id/name1"
android:layout_width="100dp"
android:layout_height="100dp" />
<TextView
android:id="@+id/name1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/name1"
android:layout_width="match_parent"
android:layout_height="match_parent" />
寬高定義為100dp凹蜈,那么最后組件的寬高就是100dp限寞,這很好理解,那wrap_content和match_parent呢仰坦,我們都知道履植,wrap_content根據(jù)組件自身的大小變化而變化,match_parent指的要占滿父視圖悄晃。那么系統(tǒng)具體是如何解析的呢玫霎?
系統(tǒng)把組件的長(zhǎng)寬定義轉(zhuǎn)換相應(yīng)的測(cè)量規(guī)則
在頁面測(cè)量的時(shí)候都會(huì)調(diào)用measureChild或measureChildWithMargins方法對(duì)組件進(jìn)行測(cè)量凿滤,已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);}
關(guān)于MeasureSpec的定義就不再這里闡述了,大家可以查看其他文章庶近。
這段代碼很短翁脆,主要就是描述了通過getChildMeasureSpec把父視圖的測(cè)量規(guī)則和組件的布局參數(shù)轉(zhuǎn)換為子視圖的測(cè)量規(guī)則,進(jìn)行測(cè)量鼻种。再來看getChildMeasureSpec方法:
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:
...
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
1反番、如果childDimension >0,指的就是組件定義了具體的大胁嬖俊(MATCH_PARENT為-1罢缸,WRAP_CONTENT為-2),例如:
<TextView
android:id="@+id/name1"
android:layout_width="100dp"
android:layout_height="100dp" />
組件的大小就是childDimension 投队,測(cè)量模式為MeasureSpec.EXACTLY枫疆。
2、如果childDimension == LayoutParams.MATCH_PARENT敷鸦,組件大小就是父視圖提供的大小size,而測(cè)量模式也是MeasureSpec.EXACTLY息楔。
3、如果childDimension == LayoutParams.WRAP_CONTENT扒披,組件的測(cè)量模式為MeasureSpec.AT_MOST值依,大小為父視圖提供的大小size,這里的大小實(shí)際上指的是組件的最大大小,組件的具體大小的測(cè)量還需要組件自己實(shí)現(xiàn)谎碍。
4、如果父視圖的測(cè)量模式為MeasureSpec.AT_MOST洞焙,及時(shí)組件childDimension == LayoutParams.MATCH_PARENT蟆淀,其測(cè)量模式也為MeasureSpec.AT_MOST。
組件如何根據(jù)測(cè)量模式測(cè)量出其大小的
child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法最終就會(huì)調(diào)用組件的onMeasure方法澡匪,在這里組件會(huì)根據(jù)測(cè)量規(guī)則計(jì)算出具體的寬高大小熔任。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getDefaultSize方法就是計(jì)算出具體的寬高,setMeasuredDimension方法則是設(shè)置寬高唁情。
基類View的具體計(jì)算方法是getDefaultSize:
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.AT_MOST還是MeasureSpec.EXACTLY模式疑苔,最終得到的具體大小都為父視圖提供大小,這與我們對(duì)wrap_content的理解是有出入的甸鸟。其實(shí)具體的組件都需要重寫onMeasure方法的惦费。
例如TextView:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
} else {
...這里計(jì)算textView的大小width....
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
}
...
setMeasuredDimension(width, height);
}
1、如果模式為MeasureSpec.EXACTLY抢韭,大小就是父視圖提供的大小
2薪贫、如果模式是MeasureSpec.AT_MOST,會(huì)取textView的真實(shí)大小與與父視圖提供的大小中小的那個(gè)刻恭,這樣與我們理解的wrap_content就吻合了瞧省。