MeasureSpec再理解
MeasureSpec封装了父view传递给子view的布局要求,代表了一组高度和宽度的要求。
一个MeasureSpec由大小(size)和(mode)组成。
模式有三种:
- UNSPECIFIED:这个一般是系统自己用到的。父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小。
- EXACTLY:当传入具体的值或者是match_parent时
- AT_MOST:当设置成wrap_content时,这时的大小是不确定的。view会根据上线来设置自己的大小。
常用函数:
- static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)
- static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)
- static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
onMeasure传入的两个参数是由上一层控件传入的大小,有多种情况,重写该方法时需要对计算控件的实际大小,然后调用setMeasuredDimension(int, int)设置实际大小。
我们需要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
一个重写的实例:
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by panhongchao on 16/7/8.
*/
public class TestView extends View {
public TestView(Context context) {
super(context);
}
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 10;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 20;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
measure的测量流程
viewGroup
重要是在onMeasure(),遍历每个子view的测量。
view
入口为ViewGroup#measureChildWithMargin()方法。
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); // 1
}
由源码可知,里面调用了getChildMeasureSpec方法,把父容器的MeasureSpec以及自身的layoutParams属性传递进去来获取子View的MeasureSpec,这也印证了子View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定这个结论。
具体的逻辑关系:

接着调用View#measure,内部再调用View#onMeasure。而这个measure是由下层的view重写的。