【Android】View绘制过程分析之measure

来源:互联网 发布:mac登陆界面变英文 编辑:程序博客网 时间:2024/06/11 05:18

读源码,分析View的绘制过程,对自定义View的开发与理解有莫大的帮助。这是写本组文章的目的所在!

绘制View的过程分为3个阶段:

  • 第1阶段,measure() 计算View应占空间大小
  • 第2阶段,layout() 分配大小和位置到View及它的子View
  • 第3阶段,draw() 绘制

首先,分析第1阶段,分析过程的注释标记在以下代码中。

/** * 通过这个方法来计算View应该占多大的空间, * 父View通过widthMeasureSpec和heightMeasureSpec这两个参数来约束了此View的空间大小 * 实际的计算工作在onMeasure(int,int)方法中实现,子类应该覆写onMeasure(int,int)方法方法提供确切的大小 */public final void measure(int widthMeasureSpec, int heightMeasureSpec) {    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||            widthMeasureSpec != mOldWidthMeasureSpec ||            heightMeasureSpec != mOldHeightMeasureSpec) {        // first clears the measured dimension flag        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;        resolveRtlPropertiesIfNeeded();        // 子类只需要覆写onMeasure(int,int)方法,来做计算View空间的相关工作        onMeasure(widthMeasureSpec, heightMeasureSpec);        //子类必须(在onMeasure(int,int)方法中)调用setMeasuredDimension()方法,否则抛IllegalStateException异常        if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {            throw new IllegalStateException("onMeasure() did not set the"                    + " measured dimension by calling"                    + " setMeasuredDimension()");        }        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;    }    mOldWidthMeasureSpec = widthMeasureSpec;    mOldHeightMeasureSpec = heightMeasureSpec;}/** * 计算此View和它的内容应占的空间大小。 * 注意1:在此方法中一定要调用setMeasuredDimension(int,int)来设置此View应占的大小 * 注意2:若是此View是布局,则要遍历所有子View,调子View的measure(int, int)方法为子View计算大小 * View类onMeasure(int, int)方法的默认实现如下: */protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {//getMeasuredWidth()和getMeasuredHeight()方法获取到的就是在这里设置的值    mMeasuredWidth = measuredWidth;    mMeasuredHeight = measuredHeight;    //或运算,标记已经调用过setMeasuredDimension(int,int)方法,在measure(int,int)方法中通过与运算来判断    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;}/** * 用于获取默认大小的工具方法 * 若父View有约束此View大小(通过measureSpec),则遵循父View分配的大小; * 否则以传进来的参数size作为大小。size怎么得来?且继续看下去。 */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应该使用的最小宽度。取值为“mMinWidth”和“背景图片最小宽度”两者中的最大值。 * 其中mMinWidth从哪里来?使用时,一是通过调用setMinimumWidth(int)方法设置;二是通过布局文件中属性android:minWidth设置 *  */protected int getSuggestedMinimumWidth() {    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}/** * Drawable类的方法,若Drawable是图片资源,有固定的宽度,则返回固定的宽度;否返回0 */public int getMinimumWidth() {    final int intrinsicWidth = getIntrinsicWidth();    return intrinsicWidth > 0 ? intrinsicWidth : 0;}    /*** getSuggestedMinimumHeight(int,int)方法同理,略。*//** * 若是此View是布局,以FrameLayout为例: */protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    //省略部分代码    //设置自己的大小    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),            resolveSizeAndState(maxHeight, heightMeasureSpec,                    childState << MEASURED_HEIGHT_STATE_SHIFT));    //遍历子View,计算子View的大小    count = mMatchParentChildren.size();    if (count > 1) {        for (int i = 0; i < count; i++) {            final View child = mMatchParentChildren.get(i);            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();          //以下操作,创建MeasureSpec传递给子View, 即FrameLayout给子View分配大小            int childWidthMeasureSpec;            int childHeightMeasureSpec;            if (lp.width == LayoutParams.MATCH_PARENT) {            //子View的宽度 = 父View的宽度 - 父View的Padding - 子View的Margin                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -                        getPaddingLeftWithForeground() - getPaddingRightWithForeground() -                        lp.leftMargin - lp.rightMargin,                        MeasureSpec.EXACTLY);            } else {                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +                        lp.leftMargin + lp.rightMargin,                        lp.width);            }            if (lp.height == LayoutParams.MATCH_PARENT) {                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -                        getPaddingTopWithForeground() - getPaddingBottomWithForeground() -                        lp.topMargin - lp.bottomMargin,                        MeasureSpec.EXACTLY);            } else {                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,                        getPaddingTopWithForeground() + getPaddingBottomWithForeground() +                        lp.topMargin + lp.bottomMargin,                        lp.height);            }            //调用子View的measure(int, int)方法。            //根据上面的计算得知:子View的measure()方法中得到的childWidthMeasureSpec不包括子View的Margin值            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);        }    }}/** * 来看一下MeasureSpec类的工具方法: * 参数size为实际大小值, * 参数mode为模式,可取值: * EXACTLY(父View确切地给定了子View的大小,不顾View可能超出这个大小) * AT_MOST(尽可能大,  The child can be as large as it wants up to the specified size.) * UNSPECIFIED(父View没有约束子View的大小,子View想要多大就多大) */public static int makeMeasureSpec(int size, int mode) {    if (sUseBrokenMakeMeasureSpec) {        return size + mode;//原来是实际大小值与模式值之和    } else {        return (size & ~MODE_MASK) | (mode & MODE_MASK);    }}


@容新华技术博客 - http://blog.csdn.net/rongxinhua - 原创文章,转载请注明出处


0 0
原创粉丝点击