onMeasure方法是View的方法,主要用于测量View大小。onMeasure方法有两个参数,分别是View宽和高的MeasureSpec。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
MeasureSpec是View中的内部类,是二进制运算,由于int是32位的,用高两位表示mode,低30表示size。
MeasureSpec.getMode(widthMeasureSpec);//通过MeasureSpec类提供的方法来获取mode
MeasureSpec.getSize(widthMeasureSpec);//通过MeasureSpec类提供的方法来获取size
由于View的大小受到它的父布局或者子View的影响,所以需要通过measureSpec来计算大小。
MeasureSpec分别有三种mode:
AT_MOST | 父容器指定了一个可用大小即 SpecSize,View 的大小不能大于这个值,具体是什么值要看不同 View 的具体实现。它对应于 LayoutParams 中的 wrap_content |
EXACTLY | 父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终大小就是 SpecSize 所指定的值,它对应于LayoutParams 中的 match_parent 和具体的数值这两种模式 |
UNSPECIFIED | 父容器不对 View 有任何的限制,要多大给多大,这种情况下一般用于系统内部,表示一种测量的状态 。 |
上面提到过View的大小受到父布局或子View的影响,所以上述的父布局三种模式,加上子View的三种LayoutParams(10dp、match_parent、wrap_content),便形成了9种情况。下面结合源码分析一下。
/**
* 源码
* 可以通过该方法来计算出childView的MeasureSpec
* @param spec 父容器的MeasureSpec
* @param padding 父容器的padding
* @param childDimension 子View设置的LayoutParams宽高
* @return 子View的measureSpec
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父容器的specMode,模式
int specMode = MeasureSpec.getMode(spec);
//父容器的specSize,参考大小
int specSize = MeasureSpec.getSize(spec);
//计算出父容器去除padding的实际可用大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY://精确大小,已经确定父容器的大小是多少
if (childDimension >= 0) {//子View已经指定了大小
//使用子View的LayoutParams大小
resultSize = childDimension;
//子View精确模式
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//子View是match_parent
//因为是子View是match_parent,父容器给予能给的全部,即父容器大小减去padding的实际可用大小,确定子View大小
resultSize = size;
//子View精确模式
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {//子View是wrap_content
//不确定子View有多大,所以还是给予能给的全部
resultSize = size;
//由于子View是wrap_content大小是不确定,所以子View最多不能超过父容器的大小
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST://WRAP_CONTENT,父容器的大小与子View相关,不确定大小
if (childDimension >= 0) {//子View已经指定了大小
//使用子View的LayoutParams大小
resultSize = childDimension;
//子View精确模式
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View是match_parent,父容器给予能给的全部,即父容器大小减去padding的实际可用大小
resultSize = size;
//因为父容器的大小是不确定的,子View充填父容器,但是不能超过父容器,所以是AT_MOST
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子View是wrap_content,不确定大小。父容器给予能给的全部,
resultSize = size;
//子View不确定大小,但是不能超过父容器大小
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED://父容器不对View做任何限制,要多大给多大,一般用于系统内部
//这个模式一般取决于父容器,一般在可以滚动的容器中会遇到,比如recycleView,因为可以滚动,导致子view的大小可能会超过容器的大小,所以使用此模式
if (childDimension >= 0) {
//使用子View的LayoutParams大小
resultSize = childDimension;
//子View精确模式
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
default:
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}