在Android中,一个View绘制出来要经过三大流程,分别用measure来测量View的宽高,用layout来确定View在父容器中的位置,最终用draw将View绘制到屏幕上。本章节主要,通过自己的理解来讲解一下第一个流程measure的相关知识点。
measure方法在View类中,在此方法中会 调用onMeasure方法,一般我们通过重写onMeasure方法去自定义View的宽高。ViewGroup类中没有实现onMeasure方法,所以在自定义ViewGroup的时候,我们要重写onMeasure方法,在onMeasure方法中对其所有子元素进行measure过程,通过这种方式将测量行为传递子view,如此类推view的测量行为,从而完成整个View树的测量工作。
讲到view的measure行为,我们首先要一个重要的类MeasureSpec,这个类主要用来保存测量模式SpecMode,规格大小SpecSize,通过一个32位的int值的高2位和低30去保存它们的值。其中SpecMode有三种模式:UNSPECIFUED、EXACTLY、AT_MOST。其中我们主要理解EXACTLY和AT_MOST:
1. EXACTLY 表示父容器已经检测出当前view的大小,就是view在SpecSize中所指定的值,它对应于LayoutParams中的match_parent和具体数值两种模式
2. AT_MOST 表示当前view的大小不超过父容器的可用大小,它对应于LayoutParams中的wrap_content。
接下来我们解决一下ViewGroup的measure过程,虽然ViewGroup没有实现onMeasure方法,但它提供了一些重要的方法用来测量child,先看一下measureChildren方法,如下所示。
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);
}
}
}
从上述代码可以看出,ViewGroup会对每一个子元素进行measure,我们再看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);
}
上面代码中主要是通过调用getChildMeasureSpec方法来获取子元素的测量值,然后通过child的measure方法将测量行为传递下去,我们看看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: //父容器精确模式
//子view是精确值
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
// 子view模式MATCH_PARENT,大小为父容器可用的最大空间
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
// 子view模式WRAP_CONTENT,大小为父容器可用的最大空间
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
//父容器AT_MOST模式
case MeasureSpec.AT_MOST:
if (childDimension >= 0) { //子view精确值
// 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);
}
代码有点长,在这里我们可以清晰地看到,child的MeasureSpec是怎么来的,怎么通过child的layoutparams值和父容器的MeasureSpec去计算得到当前child的MeasureSpec的,有一点要注意的是,默认的情况下,child的layoutparams值不管是match_parent还是wrap_content,其resultSize都是父容器可用空间的大小。
上面分析了ViewGroup的相关measure行为,主要是对子元素的测量,具体ViewGroup的大小该怎么确定,这个要自定义的时候在onMeasure方法中计算,因为ViewGroup是一个view容器,所以它的宽高是很难有统一的计算方法,不同的情况计算方法又不一样,所以ViewGroup没有去实现onMeasure方法,留给继承者去实现。
下面我们看一下View类的onMeasure方法,代码如下
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看到,默认通过getDefaultSize去获取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;
}
我们只需要看AT_MOST和EXACTLY这两种情况,所以其实此方法返回的就是specSize,而这个值就是view测量后的大小,因为specSize的值在AT_MOST的情形下,是等于父容器可用大小的,而如果在布局中使用wrap_content,specSize大小和match_parent是一样的,所以我们一般在自定义view的时候,对于warp_content的情形,都会给view指定一个自定义的大小,以免和match_parent一样。
总结一下,通过上面的分析,我们知道view的measure流程大概是怎么的一个过程,即我们首先会在ViewGroup的继承类的onMeasure方法中,要分别去测量父容器的子view,具体怎么通过测量出的child的宽高去计算ViewGroup的宽高,这个需要自己根据具体需求去实现。如果我们要子view中进行自定义的measure行为,就要重写其onMeasure方法,根据需求是实现相关测量逻辑。整个测量流程是从ViewGroup到view的从上到下的行为,从而完成了我们整个view树的测量工作。