本篇博客为大家介绍View原理中的measure过程,首先为大家介绍类MeasureSpec,下面是谷歌关于MeasureSpec的英文注释
// 一个MeasureSpec封装了从父布局传给子布局的布局要求
1、A MeasureSpec encapsulates the layout requirements passed from parent to child.
//每个MeasureSpec代表了不是宽度或者高度的要求
2、Each MeasureSpec represents a requirement for either the width or the height.
//一个MeasureSpec是由尺寸和模型组成。有三种可能的模型:
3、A MeasureSpec is comprised of a size and a mode. There are three possible modes:
UNSPECIFIED
//父类没有给子类强加任何限制,无论子类想要多大都可以达到
The parent has not imposed any constraint on the child. It can be whatever size
it wants.
EXACTLY
//父元素决定子元素的精确值.子类被限定在这个边界无论它自身想要多大
The parent has determined an exact size for the child. The child is going to be
given those bounds regardless of how big it wants to be.
AT_MOST
//子元素之多达到指定大小的值
The child can be as large as it wants up to the specified size.
在View绘制流程基本是三布走,measure(测量)—在measure()方法中调用onMeasure()方法,layout(布局),draw(绘制)
MeaureSpec其实是在View的绘制的第一步测量过程中用到的第一个参数,这个参数能初步确定一个View的宽高,当然宽高的最终大小是在layout(布局)阶段决定的。
下面是一张关于控件measure的流程图
这张流程图基本显示了控件测量的过程:
1、ViewGroup是一个继承自View的抽象类,在ViewGroup这个类没有重写measure()或者onMeasure()中的方法,其实在其子类,如不同的布局中(如LinerLayout, FrameLayout)有着不同的实现。
2、从流程图中可以看出ViewGroup中有measureChildWithMargins()或者measureChild()方法去测量该ViewGroup的子布局,最终又调用了View()中的measure()和onMeasure()方法去测量子元素的宽和高。
首先分析ViewGroup中的这两个测量方法,测量的最终目的当然是为了得到初步尺寸(MeasureSpec的尺寸或者控件的指定尺寸大小)和MeasureSpec的模型。
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);
}
这个方法里面传递的参数分别是子控件,以及父控件的MeasureSpec。在这段代码的第9行可以看到,通过getChildMeasureSpec方法可以得到子控件的MeasureSpec,
参数分别为parentWidthMeasureSpec、 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed、lp.width. 第1和第三个参数都明白,分别是父控件的MeasureSpec和子控件的LayoutParams,那么第二个参数是什么呢?
从这张图中可以看出中间的参数其实就是父ViewGroup所占用的空间,由此我们得出结论:
子控件的MeasureSpec受三个方面的影响:
1、父ViewGroup的MeasureSpec
2、父ViewGroup所占用的空间
3、子View的LayoutParams
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
//父元素是EXACTLY模式
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//该种情况子元素大小为指定的大小,Mode为EXACTLY模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//子元素的LayoutParams为MATCH_PARENT,这种情况尺寸子元素的尺寸为父元素尺寸减去父元素所占用空间的尺寸,Mode为EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
//子元素的LayoutParams是WRAP_CONTENT,大小为父元素减去所占用空间的大小,子元素的Mode为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 has imposed a maximum size on us
//父元素的Mode为AT_MOST
case MeasureSpec.AT_MOST:
//子元素的尺寸为指定大小,Mode为EXACTLY
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//子元素的LayoutParams为MATCH_PARENT,子元素尺寸大小为父元素的尺寸减去所占用空间的尺寸,模式为AT_MOST
} 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;
//子元素的LayoutParams为WRAP_CONTENT,尺寸为父元素尺寸减去父元素所占用空间的尺寸,模式为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
//父元素的模式为UNSPECIFIED
case MeasureSpec.UNSPECIFIED:
//子元素的尺寸大小为指定尺寸大小,模式为UNSPECIFIED
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.UNSPECIFIED;
//子元素的LayoutParams为MATCH_PARENT,尺寸为0,模式为UNSPECIFIED
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
//子元素的LayoutParams为WRAP_CONTENT,尺寸大小为0,模式为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);
}
无论是measureChild()或者前面介绍的measureChildWithMargins最后都是通过getChildMeasureSpec得到子元素的MeasureSpec,然后再通过View的measure()和onMeasure()方法测量。上述是getChildMeasureSpec(int spec, int padding, int childDimension) 的源代码,可以看到第一个参数是父元素的MeasureSpec,第二个元素是父元素占用的空间(根据子元素是否有Margins会略有不同,这也是为何有两个测量子元素的方法,一个有Margins,另一个没有Margins),第三个元素就是子元素的LayoutParams,至于具体的测量逻辑我直接在代码中做了注释。请看下面的表格,就是这套代码的表格形式:
看我ViewGroup里面的逻辑,仔细回看前面的流程图,下面就是View里面的measure()方法,代码如下:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
//第一次清除已经测量过的标志位
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
// measure ourselves, this should set the measured dimension flag back
//测量我们自己,设置完成之后会有一个标志位返回
onMeasure(widthMeasureSpec, heightMeasureSpec);
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
//标志位没有被设置,setMeasuredDimension没有被调用,我们增加一个异常警告开发者
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
在View的measure()方法中并没有做什么实际的对View的尺寸的设置,而且这个方法是final的,并没有办法重写,measure()方法上面的Google的英文注释:
//这个方法在发现这个尺寸多大的时候被掉调用,父元素在宽度和高度的参数上做了一个限制。
This is called to find out how big a view should be. The parent supplies constraint information in the width and height parameters.
//实际上关于一个View的测量工作是在onMeasure(int, int)方法中执行的。因此,仅仅onMeasure(int, int)方法能被子类重写。
The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
一切了然,我们来重点关注一下onMeasure(widthMeasureSpec, heightMeasureSpec)方法中的源代码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在onMeasure()方法中通过getDeaultSize得到的宽高,以及ViewGroup传过来的widthMeasureSpec和heightMeasureSpec初步确定了View的宽高以及模式。首先看一下getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)中使用到的getSuggestedMinimumWidth()方法的源代码:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
这里面出现了两个参数mMinWidth、mBackgroutd.getMiniumWidth
//这个宽度是在XML中对应的是android:minWidth的值
/**
* The minimum width of the view. We'll try our best to have the width
* of this view to at least this amount.
* 这个View的最小宽度,我们尽最大努力这个View至少这个宽度
*/
@ViewDebug.ExportedProperty(category = "measurement")
private int mMinWidth;
/**
* Returns the minimum width suggested by this Drawable. If a View uses this
* Drawable as a background, it is suggested that the View use at least this
* value for its width. (There will be some scenarios where this will not be
* possible.) This value should INCLUDE any padding.
*
* @return The minimum width suggested by this Drawable. If this Drawable
* doesn't have a suggested minimum width, 0 is returned.
* 这个Drawable建议的最小宽度,如果这个Drawable没有建议的最小宽度,返回0
*/
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
流程图:
这边已经清楚,接着看getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)的源代码:
public static int getDefaultSize(int size, int measureSpec) {
//size即为前面分析的size,如果有Drawable的背景图,为该背景图的最小建议宽度与在XML中制定的android:minWidth中较大的值,否则为在XML中指定的android:minWidth的宽度,
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//View的mode为UNSPECIFIED的时候,尺寸大小为前面所述size的值
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//View的mode为AT_MOST,EXACTLY,尺寸大小为父元素传进来的MeasureSpec里面包含的specSize
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
开发过程中三种情况:
1、父元素为ViewGroup,子元素为View
2、父元素为View,无子元素
3、父元素为ViewGroup,子元素为ViewGroup
结合ViewGroup和View测量的源代码,根据三种不同的情况总结:
1、父元素为ViewGroup, 子元素为View,如下图所示:
2、父元素为View,无子元素,情况与上述单纯分析View的测量源代码情况一致
3、父元素为ViewGroup,子元素为ViewGroup,此种情况此处暂时无法分析,因为ViewGroup本身的meaure()方法并无重写,在具体不同的布局(LinearLayout、RealtiveLayout)有着不同的实现。