View的结构
Android中的View以树的结构来进行管理,从如中可以看到,最顶层的Window到ContentView,这些都是我们在实际开发过程中比较少用到的。
MeasureSpec
MeasureSpec是一个int类型的变量,以下为MeasureSpec的部分源代码:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
可以看到MeasureSpec用最高的两位用来存放测量的模式:
UNSPECIFIED:大小不确定的
EXACTLY:大小确定了具体的
AT_MOST:大小的上限,即最大不能超过这个
ViewGroup的测量
View一开始从DecorView开始进行measure,由于DecorView本质是FrameLayout即就是ViewGroup,所以我们一开始从ViewGroup开始讲onMeasure。
ViewGroup从被measure调用之后开始调用onMeasure开始测量自己;
ViewGroup从onMeasure函数中获得的widthMeasureSpec和heightMeasureSpec,根据这两个变量的值来进行对子View的测量。遍历子View更具子View的LayoutParam进行下一步的MeasureSpec计算,即调用getChildMeasureSpec(measureSpec,padding,size);将得到的结果传入到measure中进行子View的onMeasure。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
super.onMeasure(widthSpec, heightSpec);
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
LayoutParams lp = view.getLayoutParams();
int childMeasureWidth = getChildMeasureSpec(widthMeasureSpec,0,lp.width);
int childMeasureHeight = getChildMeasureSpec(heightMeasureSpec,0,lp.height);
view.measure(childMeasureWidth,childMeasureHeight);
//measureChild(view, widthSpec, heightSpec);
}
}
这里假设ViewGroup的大小是确定的且为宽高都为500个像素,为什么是500个像素,后面再讲。接着往下走,遍历每一个子View,根据子View的布局参数来计算以下的子View的测量规格即调用了getChildMeasureSpec,接着将处理过的测量规格传给子View进行measure,接着跟踪进measure,measure中代码很长包含了measure缓存的处理比较复杂,这里就不贴出来了,view的measure代码大致上就是调用了View的onMeasure,接着看看View的onMeasure是怎么实现的:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看到直接通过setMeasuredDimension把size提交上去了,这里的getDefaultSize和getSuggestedMinimumWidth依次:
根据传入的测量规格和建议的默认大小进行实际的大小确定:
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;
}
默认建议的大小是根据View的背景大小来确定的,如果背景Drawable是空的话那就是默认的mMinHeight的值,该变量默认值是0,否则的话就取背景的最小尺寸
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
通过以上步骤,View的测量就完成了,当然,对于子View中的ViewGroup的测量,递归上述过程就OK了。
补充
measureChild
在View的measure中:
int childMeasureWidth = getChildMeasureSpec(widthMeasureSpec,0,lp.width);
int childMeasureHeight = getChildMeasureSpec(heightMeasureSpec,0,lp.height);
view.measure(childMeasureWidth,childMeasureHeight);
这段代码可以替换成:
measureChild(view, widthSpec, heightSpec);
他俩的实际作用是差不多的,查看系统源码可以一目了然的看到:
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);
}
measureChildren
measureChildren实际上是遍历各个子View,然后分别调用measureChild:
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);
}
}
}