简介
组件在页面上的展示要经过测量、布局、绘制三个过程,而测量就是把我们定义的组件宽和高转换为具体的大小的这个过程。
我们常见的组件的宽高定义如下:
<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根据组件自身的大小变化而变化,match_parent指的要占满父视图。那么系统具体是如何解析的呢?
系统把组件的长宽定义转换相应的测量规则
在页面测量的时候都会调用measureChild或measureChildWithMargins方法对组件进行测量,已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);}
关于MeasureSpec的定义就不再这里阐述了,大家可以查看其他文章。
这段代码很短,主要就是描述了通过getChildMeasureSpec把父视图的测量规则和组件的布局参数转换为子视图的测量规则,进行测量。再来看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 ,测量模式为MeasureSpec.EXACTLY。
2、如果childDimension == LayoutParams.MATCH_PARENT,组件大小就是父视图提供的大小size,而测量模式也是MeasureSpec.EXACTLY。
3、如果childDimension == LayoutParams.WRAP_CONTENT,组件的测量模式为MeasureSpec.AT_MOST,大小为父视图提供的大小size,这里的大小实际上指的是组件的最大大小,组件的具体大小的测量还需要组件自己实现。
4、如果父视图的测量模式为MeasureSpec.AT_MOST,及时组件childDimension == LayoutParams.MATCH_PARENT,其测量模式也为MeasureSpec.AT_MOST。
组件如何根据测量模式测量出其大小的
child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法最终就会调用组件的onMeasure方法,在这里组件会根据测量规则计算出具体的宽高大小。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getDefaultSize方法就是计算出具体的宽高,setMeasuredDimension方法则是设置宽高。
基类View的具体计算方法是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模式,最终得到的具体大小都为父视图提供大小,这与我们对wrap_content的理解是有出入的。其实具体的组件都需要重写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 {
...这里计算textView的大小width....
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
}
...
setMeasuredDimension(width, height);
}
1、如果模式为MeasureSpec.EXACTLY,大小就是父视图提供的大小
2、如果模式是MeasureSpec.AT_MOST,会取textView的真实大小与与父视图提供的大小中小的那个,这样与我们理解的wrap_content就吻合了。