Android ViewGroup测量child过程

首先,测量有三种mode(模式):
1.UNSPECIFIED: 美其名曰不具体的测量,父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态,我们应用程序貌似都没涉及到。对应的具体数值为:00000000 00000000 00000000 00000000

2.EXACTLY:顾名思义是具体的、精确的测量,在布局中对应match_parent和具体数值两种模式,如下:
android:width=”100dp”
android:height=”match_parent”
上面两种都是EXACTLY模式,因为match_parent就是父容器大小,测量子类的时候父容器大小已经是测量好的。对应的数值为01000000 00000000 00000000 00000000

3.AT_MOST:意如其名,最多不能超过指定大小,这个指定大小指的是父元素测量好自己之后给子元素指定的大小,一般是父元素剩余可用空间。在布局中对应:wrap_content,如下:
android:width=”wrap_content”
对应的数值为10000000 00000000 00000000 00000000

MeasureSpec用一个32位的int值代替,高两位代表测量模式(UNSPECIFIED,EXACTLY,AT_MOST),低两位代表测量的大小,例如一个通过makeMeasureSpec()得到一个int值为 01000000 00000000 00000000 00001111,则他的测量mode是高两位01也就是EXACTLY精确的大小,具体大小为低30位,000000 00000000 00000000 00001111 大小为15

首先测量的是mDecorView(FrameLayout),ViewRootImpl中有一个代码片段

        if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

desiredWindowWidth/desiredWindowHeight 是屏幕的宽/高

getRootMeasureSpec的实现如下:

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

通过以上代码可以知道mDecorView的测量过程,如果是match_parent、wrap_content,大小就是屏幕的大小,如果是其他情况大小就是指定的尺寸大小。

测量普通的View,他的measure过程都有mDecorView一层一层传递进来的。
测量某个具体的子类源码(在measure的时候遍历每个子类,遍历到每一个子类的时候都会调用到这个方法):

    /**
    * 根据父类的MeasureSpec值、padding值和child的width、height、Margin值计算出子类的
    * childWidthMeasureSpec和childHeightMeasureSpec ,然后调用子类的measure()测量出最终大小
    */
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

看一下是如何计算出子类的测量值:

    /**
    * 父类分三种情况UNSPECIFIED、EXACTLY、AT_MOST,子类width和height也分这三种情况
    */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //得出父类的测量模式和大小
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
    //减去父类已经占有的空间,这个变量用padding命名貌似不太合适,可以用withUsed或heightUsed,得出可用大小
        int size = Math.max(0, specSize - padding);
    //定义测量出的mode和size变量
        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
    //父布局的Mode是EXACTLY
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
            //父控件是固定大小child也是固定大小,那没的说android:with定义多大就给它多大,这种情况可能child的宽度高度大于parent的高度宽度,但是超出部分显示不出来。
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //父控件是固定大小,child是填充parent,所以把剩余空间都给它,模式也是EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //父控件是固定大小,child是wrap_content,把剩余空间都给它,模式是AT_MOST,代表大小不能超过这个值。这种情况下跟resultSize跟match_parent效果一样。如果写自定义View要自己适配这种情况。
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //父布局的Mode是AT_MOST
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //父布局的Mode是UNSPECIFIED
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //通过resultSize、resultMode合并成MeasureSpec
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

只有第一个case添加了注解,后面两个参考第一个理解,就明白了child是如何根据parent和自身的mode来测量的。子类利用getChildMeasureSpec()得到的值去测量自己。

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

measure的实现如下:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
         ......
         onMeasure(widthMeasureSpec, heightMeasureSpec);
     ......
    }

measure会调用onMeasure,onMeasure的实现如下:

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

onMeasure通过调用setMeasuredDimension(int measuredWidth, int measuredHeight)去设置,而measuredWidth/measuredHeight是通过getDefaultSize()方法计算出来的,看一下getDefaultSize()的实现:

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        //这两个是上面通过getChildMeasureSpec()方法得到的mode和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;
    }

大多数情况都会是MeasureSpec.AT_MOST和MeasureSpec.EXACTLY,所以我们先认为getDefaultSize返回的就是getChildMeasureSpec()计算出来的大小,也就是specSize。

getDefaultSize(int size, int measureSpec) 中size参数是通过getSuggestedMinimumWidth()函数得到的,看一下他的实现:

    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

如果这个背景(mBackground )为null,则返回mMinWidth,如果该View设置了背景,则返回mMinWidth和mBackground.getMinimumWidth()的较大值。
mMinWidth是View构造函数解析XML的时候给的值,给出代码片段:

 case R.styleable.View_minWidth:
      mMinWidth = a.getDimensionPixelSize(attr, 0);

从上面可以看出它的值在布局里面是 android:minWidth=”50dp”给的值,如果没有定义这个属性,默认值是0,接下来看一下mBackground.getMinimumWidth(),调用到了Drawable的值:

    public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }

返回的是mBackground背景宽度,默认返回0。
所以这样分析下来getDefaultSize(int size, int measureSpec)执行的返回结果有三种情况:
1.0
2.背景图片大小
3.通过getChildMeasureSpec()计算过后的大小
然后调用 setMeasuredDimension()方法最终设置大小:

    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;
        }
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

setMeasuredDimension做的事情也就是给View的两个成员变量measuredWidth和measuredHeight赋值了,供绘制的时候参考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值