Android View 绘制流程之测量(一)

来源:互联网 发布:php设为首页代码 编辑:程序博客网 时间:2024/05/16 23:58

View树的绘制流程是从ViewRoot的performTraversals()开始的,树的遍历是有序的,由父视图到子视图,每一个 ViewGroup 负责绘制它所有的子视图,而最底层的 View 会负责测绘自身。ViewRootImpl是ViewRoot的实现类,在ViewRootImpl的performTraversals中顺序调用了performMeasure()、performLayout()、performDraw()三个方法,分别对应测量、布局、绘制三个过程。

1、测量

ViewRootImpl的performMeasure()调用了View的measure()方法,此方法为 final 类型,不可被复写,但 measure 调用链最终会回调 View/ViewGroup 对象的 onMeasure()方法。

如果ViewGroup中包含多个子View,则每个子View都要测量一次,ViewGroup中的measureChildren()负责测量子视图的大小:

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

measureChildren()遍历子视图后,调用了measureChild()方法测量每一个子视图,measureChild()方法又调用了View的measure()方法。

protected void measureChild(View child, int parentWidthMeasureSpec,            int parentHeightMeasureSpec) {        final LayoutParams lp = child.getLayoutParams();        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom, lp.height);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

View的measure()方法接收两个参数,childWidthMeasureSpec和childHeightMeasureSpec,这两个值分别用于确定视图宽高的规格和大小。

MeasureSpecs 是测量的规格,包含测量要求和尺寸的信息,其值由specSize和specMode共同组成的,有三种模式:
UNSPECIFIED
父视图不对子视图有任何约束,它可以达到所期望的任意尺寸。比如 ListView、ScrollView,一般自定义 View 中用不到,
EXACTLY
父视图为子视图指定一个确切的尺寸,而且无论子视图期望多大,它都必须在该指定大小的边界内,对应的属性为 match_parent 或具体值,比如 100dp,父控件可以通过MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸。
AT_MOST
父视图为子视图指定一个最大尺寸。子视图必须确保它自己所有子视图可以适应在该尺寸范围内,对应的属性为 wrap_content,这种模式下,父控件无法确定子 View 的尺寸,只能由子控件自己根据需求去计算自己的尺寸,这种模式就是我们自定义视图需要实现测量逻辑的情况。

onMeasure()才是测量View大小的地方,会调用getDefaultSize()来获取View的大小:

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

getDefaultSize()根据MeasureSpec获取视图的宽高:

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

然后把得到的View宽高设置给setMeasuredDimension(),setMeasuredDimension()调用了setMeasuredDimensionRaw(),把宽高赋值给mMeasuredWidth、mMeasuredHeight;

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {        boolean optical = isLayoutModeOptical(this);        if (optical != isLayoutModeOptical(mParent)) {            Insets insets = getOpticalInsets();            int opticalWidth  = insets.left + insets.right;            int opticalHeight = insets.top  + insets.bottom;            measuredWidth  += optical ? opticalWidth  : -opticalWidth;            measuredHeight += optical ? opticalHeight : -opticalHeight;        }        setMeasuredDimensionRaw(measuredWidth, measuredHeight);    }
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }

至此,View的测量过程结束,此时调用View的getMeasuredWidth()、getMeasuredHeight()可以获得测量的宽高。

0 0
原创粉丝点击