上一篇文章讲解了整个Android应用程序的View视图的顶级节点DecorView的Measure过程,文章最后就讲到了DecorView的onMeasure方法中调用super.onMeasure(widthMeasureSpec, heightMeasureSpec);之后,在FrameLayout的onMeasure方法中通过循环遍历子元素,从而往下进行每一级View的Measure过程。
在开始本文内容讲解之前,我们需要先理解一些概念。在Android源代码中,我们可以发现,所有的View控件都是直接或间接继承自View这个类,包括ViewGroup也是。所以这里我们有必要进行一下区分,我们知道ViewGroup是可以有子元素的View,那么对于不能有子元素的View,我们称之为普通View,例如TextView等。区分好了这两者,就继续来讲解ViewGroup和普通View的Measure过程。
回顾第一篇文章中,最后部分说到,DecorView的Measure过程是在performMeasure方法中调用DecorView的measure方法开始,而子元素的Measure过程是在DecorView的父类FrameLayout中的onMeasure方法中调用子元素的measure方法开始的。这就可以知道,不管是哪个ViewMeasure过程,一定会调用到它的measure方法。查看源码发现,这个measure方法是在View类中定义的,并且它是final类型,任何子类都不能重写这个方法,那我们就来看一下这个方法的源码。
//View.java 21961行
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
//光学边界处理
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
//...
if (forceLayout || needsLayout) {
//...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//...
}
}
在View的measure方法代码中,可以看到,前面有部分是光学边界处理的。至于这光学边界是什么来的,我还不是很了解,但是这里还是提一下,防止以后再看代码纠结。代码省略部分大多是判断条件的处理,我认为不需要过多关注,所以注释了。这里其实重点是onMeasure和setMeasuredDimensionRaw这两个方法。整个measure方法中重点就是,根据判断条件的不同,分别调用这两个方法。根据注释及相关代码,我猜测应该是,为了避免重复测量,所以会在测量的时候静得到的测量值缓存下来,然后下一次再需要测量的时候,检查是不是有缓存的测量值。有得话就拿出来调用setMeasuredDimensionRaw方法就行,没有就调用onMeasure方法进行测量,值的一提的是onMeasure方法的参数就是父容器的MeasureSpec值,这个是可以在onMeasure方法注释中了解到。下面来看View的这两个方法。
//View.java 22115行
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
//View.java 22089行
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
//View.java 22072行
* @param widthMeasureSpec Horizontal space requirements as imposed by the parent
* @param heightMeasureSpec Vertical space requirements as imposed by the parent
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看到,setMeasuredDimensionRaw方法很简单,将拿到的测量值,赋给全局变量就完成了。onMeasure方法最终也是调用setMeasuredDimension方法,这里又是光学边界处理,然后调用setMeasuredDimensionRaw方法,也是把处理得到的值给到全局变量。
到这,可以很直接的说,所有View的measure过程的最终目的就是把测量得到的值赋值给mMeasuredHeight和mMeasuredWidth。很显然View的测量结果,就是为了View的布局(layout)过程和绘画(draw)过程服务的。在这两个过程,都有使用到getMeasuredWidth和getMeasuredHeight方法来获取他们终的SpecSize。代码如下,之所以需要“与运算”,是因为mMeasuredWidth和mMeasuredHeight包含测量模式和测量大小,这里只取大小。
//View.java 13626行
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
//View.java 13654行
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
View的measure过程到这,其实就可以看出一些东西了。接下来的步骤就是具体的实现View重写onMeasure方法,然后进行相应的处理了。那么当我们自定义View的时候重写的onMeasure方法,到底该做些什么呢?我们在这里是不得而知的。关于这个问题,我们可以参看目前Android已经写好的控件,看看这些控件是如何实现的,就可以知道,当我们自定义View的时候该怎么做了。
先来看一下,默认情况下View的onMeasure方法是怎么做的,看源代码:
//View.java 22072行
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//View.java 22232行
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//Drawable.java 1052行
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
//Drawable.java 1026行
public int getIntrinsicWidth() {
return -1;
}
在onMeasure方法中,setMeasuredDimension方法传递的参数是getDefaultSize方法返回来的,getDefaultSize方法的参数又是getSuggestedMinimumWidth和父容器的widthMeasureSpec(这里只分析width的情况,height的情况类似)。在getSuggestedMinimumWidth方法中,查看背景是否设置,如果没有设置背景,那么View的宽度是mMinWidth,而mMinWidth对应android:minWidth这个属性的值,如果这个属性没有设置,那么默认0。如果View设置了背景,那么getSuggestedMinimumWidth方法返回的值是max(mMinWidth, mBackground.getMinimumWidth())。通过代码得知,mBackground.getMinimumWidth()返回值为intrinsicWidth,intrinsicWidth的值是Drawable的原始宽度,有些Drawable由原始宽度,有些没有,如果没有则直接返回0。
//View.java 22188行
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;
}
getDefaultSize方法也很简单, View的宽高就是specSize(不考虑MeasureSpec.UNSPECIFIED模式的情况)。所以在默认的onMeasure方法中,View的宽高就是父容器剩余的大小,也就是父容器的MeasureSpec值的SpecSize。由此我们得出一个结论:不管父容器的测量模式是什么,默认情况下,子元素的测量大小,都是父容器剩余的大小。到这了解了默认情况下View的onMeasure方法的工作。下一篇文章,将会学习ViewGroup和普通View的onMeasure方法的工作。