在View的绘制流程中,onMeasure是负责测量控件的大小的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
他的两个参数分别代表宽和高,接下来我们要看下这两个参数的意义以及他们究竟是如何生成的
首先这两个参数都是一个32位的int值,其中高2位代表了SpecMode,低30位则代表了SpecSize
SpecMode指的是测量模式
private static final int MODE_SHIFT = 30;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
共有三种模式,分别为UNSPECIFIED,EXACTLY 和AT_MOST,第一个一般不怎么用,EXACTLY 表明控件的宽高是确定的,AT_MOST表明父控件有指定的宽高,而该控件的宽高则不允许大于父控件的宽高,我们一般重写onMeasure都是针对于这种情况来设定控件的宽高
SpecSize就很简单了,就是该控件的宽高
我们接着看看MeasureSpec是如何生成的
布局的绘制起始于ViewRootImpl的performTraversals方法,而在performTraversals中测量流程最先始于performMeasure方法
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
performMeasure调用了最顶层的View(也就是DecorView)的measure,最开始宽高的MeasureSpec来自于getRootMeasureSpec方法,先来说下参数:mWidth,mHeight是我们屏幕的宽高;而lp
WindowManager.LayoutParams lp = mWindowAttributes;
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
则是一个无参的LayoutParams,他的默认宽高都是MATCH_PARENT
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
接着看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;
}
public static int makeMeasureSpec(int size,int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
很简单,就是判断下lp的宽高类型,然后简单的相加,就生成了我们最初的MeasureSpec
接着来看看之后的MeasureSpec是如何生成的,在ViewGroup中有一个getChildMeasureSpec方法负责生成子View的MeasureSpec
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:
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);
}
还是先来说下这个方法的三个参数
spec:这个ViewGroup的MeasureSpec
padding:就是我们在xml文件定义的padding参数
childDimension:子View的LayoutParams,也就是我们写的WRAP_CONTENT,MATCH_PARENT这些
其实逻辑也很清楚,子View的MeasureSpec是由父控件的MeasureSpec和子View本身的LayoutParams共同决定的,因为MeasureSpec的Mode有三种,而LayoutParams的参数也可以分为三种,即WRAP_CONTENT,MATCH_PARENT和具体数值,所以上面有9种方式生成了子View的SpecMode和SpecSize,最后调用makeMeasureSpec方法生成了子View的MeasureSpec
最后来个表,表明子View的MeasureSpec的创建规则