首先来说下一个int值,它被定义在VIew内部类里。叫MeasureSpec, 俗称测量规格。
int mode =MeasureSpec.getMode(widthMeasureSpec);
int size =MeasureSpec.getSize(widthMeasureSpec);
高2位代表一个SpecMode. 表示测量模式,低30位代表SpecSize.表示测量大小,分别可以通到以上方法取出。
当然也可以生成
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
Mode有3种,
EXACTLY 父容器已经检测出子View的精确大小,在该模式下View的测量大小为SpecSize. 精准模式
AT_MOST 父容器未能检测出子View的大小,但可以给定一个最大值,子View测量大小不大于这个值。 至多模式
UNSPECIFIED. 父容器不对子View的大小做限制。 不限制模式
对于不同的类型,测量过程分为几种,一般来说,一个View的宽高是由自身的layoutParams和父容器的MeasureSpec一起决定的。
1. 顶级View DecorView
对于顶级View,它的宽由屏幕宽度和自身layoutparams的宽度决定,高也是如此,其实和一般的View差不多。
2.ViewRootImpl
3.ViewGroup
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);
}
}
}
代码里可以看出,对于子View,如果不隐藏,则会根据父View的测量规格计算出子View的。
来看一下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);
}
里面是计算了子View的MeasureSpec,然后调用子View的measure方法。上面第二个参数只传了这个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);
}
重点看getChildMeasureSpec,该方法根据父容器的MeasureSpec和子View的layoutParams生成了子View的MeasureSpec.
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:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
第一步:
得到父容器的specMode和specSize,请参见第2-3行代码。
第二步:
得到父容器在水平方向或垂直方向可用的最大空间值,请参见第5行代码。
第三步:
确定子View的specMode和specSize,请参见第10-50行代码。
来看下switch语句,根据父容器的模式来分别处理
一: 精准模式下exactly,去判断layoutParams的参数,有3种情况 >0,-1,-2.
/**
* Special value for the height or width requested by a View.
* MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. Introduced in API Level 8.
*/
public static final int MATCH_PARENT = -1;
/**
* Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
public static final int WRAP_CONTENT = -2;
1.大于0,代表我们的layoutParams里定义了具体的像素。所以测量的子View宽或者高是精准模式,大小 =childDimension
2.MATCH_PARENT下,子View想要填满父View,所以子View也是精准模式,且大小和父View剩余空间一样 =size
3.WARP_CONTENT下,子View想要自己定义大小,但不能大于父View的大小,所以模式是至多模式,大小 =size
二: 至多模式下at_most
1.大于0,子View是精准模式,大小为 =childDimension
2.MATCH_PARENT下,子View想要填满父View,但父View也不是具体多大,所以认作不超过父View,模式是至多模式,大小= size
3.warp_content下,也是一样的至多模式,大小 =size三: 不限制模式 UNSPECIFIED
1.大于0,子View是精准模式,大小为 =childDimension
2.MATCH_PARENT,子View是是不限制的,所以是不限制模式,且大小为0.
3.同2上
上面的各个子View的measure方法中都会调用到onMeasure方法, 我们就重点看这个方法。这个方法是View的,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
set方法表示设置宽高,我们重点看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;
}
看代码,第一个参数其实只在不限制的模式下才用,对于其他2个模式,都是取传入的measurespec里面的大小。看一下getSuggestedMinimumWidth方法,表示取的是背景和minWidth中的较大值,我们可能在listView里设置过minWidth这个属性,因为他就是使用的是不限制模式。这里就不深入了
还有一个结论。
直接继承View类的自定义控件需要重写onMeasure方法并设置warp_content时的自身大小。因为在至多模式下,系统默认取了最大的那个值,也就是父容器所剩余的空间值,和match一样大,而在ImageView 和TextView里都有特殊处理。故在自定义View继承View的情况下用到wrap需要做一些处理,如下;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*
* this.getLeft(); //获取与父容器左边距离 this.getRight(); //获取与父容器右边距离 int width
* = this.getRight()-this.getLeft();
*/
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
if(modeWidth==MeasureSpec.AT_MOST&&modeHeight==MeasureSpec.AT_MOST) {
setMeasuredDimension(100, 100);
}
else if (modeWidth==MeasureSpec.AT_MOST) {
setMeasuredDimension(100, sizeHeight);
}
else if(modeWidth ==MeasureSpec.AT_MOST) {
setMeasuredDimension(modeWidth, 100);
}
// int measureSpec = MeasureSpec.makeMeasureSpec(size, mode);
}
考虑到ViewGroup里嵌套这ViewGroup,所以还得看下ViewGroup的onMeasure方法,但它是一个抽象类,它的onMeasure方法都在子类里实现,考虑到不同的布局特性,比如一些weight等特殊性,。
列举一下
1.线性布局
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}