前言
说起Android的自定义view,各位老油条肯定都不陌生了,我也是在最近重温《安卓开发艺术探索》的时候才发现的这个问题,**onMeasure的参数 MeasureSpec 到底表示了自身的属性还是父view的属性?**我问了身边的小伙伴,他们的回答都不对或者很模棱两可,所以我特意记录一下,希望小伙伴们能有个准确地认识。
结论先行
先说下结论,onMeasure是测量view或viewGroup时系统调用的,它的参数widthMeasureSpec和heightMeasureSpec既不代表父ViewGroup的宽高也不代表子View的宽高,它表示了父ViewGroup对自己的期望,至于子View实际的宽高得看setMeasuredDimension传的是什么。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ...
}
具体分析
那么什么是父view的期望值?我们从头说起,在android绘制的过程中,view树的最顶层是ViewRootImpl,它是一个实现了ViewParent接口的特殊的类,我们可以把它看成所有View的爷爷,ViewRootImpl中又存着DecorView的实例,我们知道DecorView才是我们View树最上层的ViewGroup,系统会调用ViewRootImpl的performTraversal方法开始View树的绘制,这个方法里面会调用DecorView的measure方法,到此,View的绘制正式传递到DecorView中
好,停一下,ViewRootImpl传递给DecorView的measure方法中的参数是怎么来的?我们看一下performTraversal方法内部这一小段:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
mWidth,mHeight指的是屏幕的宽高,lp是一个LayoutParams类型的参数,他表示当前window的尺寸参数,那么也就是说这里的值都是当前屏幕的参数,关键还得看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.