Android UI 绘制过程浅析(二)onMeasure过程

时间:2022-03-19 09:16:20

前言

  View的绘制过程分为 measure、layout、draw三个步骤,接下来对这三个步骤逐一进行研究。

measure方法的签名

public final void measure(int widthMeasureSpec, int heightMeasureSpec);

  measure方法用来测量View的尺寸。两个参数widthMeasureSpec/heightMeasureSpec声明了Parent View所能提供的宽/高。

  需要注意的是,widthMeasureSpec/heightMeasureSpec 这两个 int 型的参数是复合参数(compound parameters)。jvm中以32bit来存储int,最高两位表示“mode”,后面30位才是具体的数值。在View.java中有下面两个方法

        /**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
} /**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

  看到了吧,使用位操作&来获取最高两位的mode。2^2=4,这里只有用到了3种,它们分别的含义入下

  • UNSPECIFIED-ParentView对子View没有约束,后者可以是任意大小
  • AT_MOST-ParentView规定了尺寸上限,子View的大小不可以超过这个上限
  • EXACTLY-ParentView规定了子View的宽高,子View只能是这个宽高

  measure方法本身是声明为final的,CustomView必须实现自己的onMeasure方法,以便View.measure对此进行回调。在实现自己的CustomView.onMeasure之前,先来看看View中默认的onMeasure方法。

onMeasure

  在自定义View中,往往需要重写onMeasure方法,来设置View的measured width/height。要注意的一点是,计算完成后,必须调用setMeasuredDimension(int, int)来设置宽高,不然在上层measure方法会抛出IllegalStateException。自定View计算出的宽/高必须满足最小宽/高(通过getSuggestedMinimumWidth()、getSuggestedMinimumHeight()获取)。当然也可以在自定义View的onMeasure方法中直接调用View.onMeasure,如下

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

  然而,View默认的onMeasure实现逻辑非常简单,恐怕不能满足自定义View的需求。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

  View.onMeasure简单粗暴地调用了setMeasuredDimension来设置measuredWidth/measuredHeight。其中使用的getDefaultSize(int, int)方法如下

    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;
}

  逻辑很简单,一个switch/case语句,根据SpecMode来判断返回。若为UNSPECIFIED,则返回minimumWidth;若为AT_MOST/EXACTLY,返回SpecWidth。

  到此为止,View的measure过程已经完成了。对于ViewGroup,可以猜想,它的onMeasure方法,是把所有子View的onMeasure做一次相加。我们来看一下具体是不是这样做的。

ViewGroup.measure

  ViewGroup继承了View类,理所应当地,我们试图在ViewGroup中查找是否重写了onMeasure方法——未果。不过我们惊喜地发现了measureChildren这个方法——用来遍历所有children,对每一个child进行measure

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}

  measureChild中,将ViewGroup的padding加入了计算,最后调用child.measure,这里不予深究,也不再贴出代码。

  ViewGroup.java本身并没有重写onMeasure方法,在它的具体实现中,onMeasure根据具体情况,将children的measured size进行求和处理,具体可以参阅FrameLayout/LiearLayout/RelativeLayout 的代码。

小结

  对于measure过程的分析就到这里,下一篇我们继续探讨onLayout过程。