以LinearLayout為例瘦麸,子View是一個自定義View壕探。這樣可以在onMeasure方法
重寫打印日志便于分析
如果LinearLayout是第一層View切揭,并且填滿屏幕;
在LinearLayout中,onMeasure接收的參數(shù)就是exactly模式伪煤;
具體分析如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
高度參數(shù)heightMeasureSpec:
32位整數(shù)
前2位:模式,有三種情況{UNSPECIFIED, EXACTLY, AT_MOST}
后30位:具體大小
這樣1個int值可以用于2種用途;
工具類代碼如下:
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
其實就是位運算:
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
跟蹤LinearLayout.onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
measureVertical() {
for (int i = 0; i < count; ++i) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
}
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
大致就是依次對每一個子View測量
對于measureChildWithMargins
是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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
這里父布局測量凛辣,需要把父親自己的模式和子控件的LayoutParam參數(shù)合并出來抱既,
結(jié)合三種父布局模式,算出子控件建議大小扁誓,建議模式防泵,然后調(diào)用child.measure
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;
case MeasureSpec.UNSPECIFIED:
...
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
setMeasureDimension()就是設置當前view的大小
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
當然不重寫則是默認配置:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
默認測量規(guī)則如下:也是根據(jù)模式來的,當然也有一個最小值蝗敢,具體分析一下吧
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;
}
總結(jié):父布局測量的時候捷泞,會有參數(shù)寬和參數(shù)高,依次
對子view測量寿谴,如果調(diào)用measureChildWithMargin,則根據(jù)自己本身的
和子控件的LayoutParam參數(shù)共同來算出子控件的建議高度锁右,寬度同理;
具體分析,如果一個matchParent的linearlayout里面有一個wrapcontent的子view
本身在linearlayout測量是Exactly則Exactly+wrapcontent=AT_MOST+父布局高度
所以這時候子控件不重寫測量方法高度默認就是和父布局一樣大小