在谈View的Measure过程之前,我们先谈谈MeasureSpec类。
我们直接说结论:
1.一个View的widthMesureSpec和heightMeasureSpec决定了这个View的宽和高。
2.子View的width/heightMeasureSpec值是由父View的width/heightMeasureSpec值和子View的宽/高LayoutParams计算得到,而子View的widthMesureSpec和heightMeasureSpec则决定了子 View宽和高,如下图:
下面开始介绍MeasureSpec类:
MeasureSpec类
MeasureSpec类是一个测量规格类,测量规格类(MeasureSpec) = 测量模式(mode) + 测量大小(size)。
测量模式mode有三种,EXACTLY,ATMOST和UNSPECIFIED。
EXACTLY : 表示父View强加给子View的宽/高一个确切的size即父View的宽/高的size
ATMOST : 表示父View强加给子View的宽/高一个最大的size即父View的宽/高的size
UNSPECIFIED : 表示父View对子View的宽/高没有限制
获取子View的MeasureSpec源码如下:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //父View的测量模式
int specSize = MeasureSpec.getSize(spec); //父View的大小
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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
我们可以清楚地看到,通过从父View的width/heightMeasureSpec得到specMode和specSize,再加上子类自己的宽/高LayoutParams这三个因素决定了子View的resultMode和resultMode,从而决定了子View的width/heightMeasureSpec。
可能细心的同学会注意到一个问题,按这样的说法,当子类的宽/高取match_parent和wrap_content得到的子View宽/高的大小都是父View的specSize,即父View的宽/高的大小,这怎么可能 ?我们平时使用可不会的时候match_parent和wrap_content是不同的啊!好了,这里可以明确告诉大家一个结论:自定义View,如果没有重写onMeasure()方法处理wrap_content的情况,则wrap_content则会不起作用 !
View的Measure流程可分为两类,一类是View的Measure,一类是ViewGroup的Measure。
View的Measure过程
代码
1.将从父View计算得到的widthMeasureSpec和heightMeasureSpec传递给onMeasure()
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
}
2.调用setMeasuredDimension()保存子View的宽和高大小,这里的宽和高的大小通过getDefaultSize()得到
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
3.通过解析传入的width/heightMeasureSpec,得到子View的宽/高。另外这里的getSuggestedMinimumWidth()是规格为子View测量规格为UNSPECIFIED取宽/高的大小。
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;
}
流程图
ViewGroup的Measure过程
代码
1.将从父View计算得到的widthMeasureSpec和heightMeasureSpec传递给onMeasure()
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
}
2.由于不同的布局具有不同的特性,不同的特性导致计算子View宽/高的方式不同,所以自定义ViewGroup需重写onMeasure()方法,一般包括以下步骤:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1.遍历子View进行测量 measureChildren(widthMeasureSpec, heightMeasureSpec)
// 2. 合并所有子View的宽/高大小,最终得到父View的width/heightMeasureSpec
// 3. setMeasuredDimension()保存父View的宽和高的大小
}
3. 在measureChildren()方法中遍历子View并调用measureChild()方法对子View进行测量
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
// 遍历所有子view
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 1. 获取子View的布局参数
final LayoutParams lp = child.getLayoutParams();
// 2. 获取子View的width/heightMeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSc mPaddingTop + mPaddingBottom, lp.height);
//3.对子View进行测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
流程图
计算子View的width/heightMeasureSpec需要父View传递过来的的父View的width/heightMeasureSpec,那么最外层的view也就是顶层view--DecorView的width/heightMeasureSpec是怎么传递进来的呢?
从ViewRootImpl这个类中可以看到
...
int childWidthMeasureSpec = getRootMeasureSpec()
int childHeightMeasureSpec = getRootMeasureSpec()
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec)
...
看这个getRootMeasureSpec()方法的源码如下:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
以上。