自定义View一般都需要重写onMeasure()、onLayout()、onDraw()。但自定义ViewGroup更重要的是onMeasure()和onLayout(),因为作为容器,onDraw()方法显得没那么必要,即时你重写了此方法,它也不会调用,毕竟容器只需要关注自己内部view的摆放和约束其大小而已。

一、onMeasure如何测量child?

我们在重写完

onMeasure(int widthMeasureSpec, int heightMeasureSpec)

内部有widthMeasureSpec和heightMeasureSpec两个参数,通过它我们可以调用

     int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
     int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

获取当前view的建议值和mode,view的实际大小由建议值和mode决定;而这个mode有以下三种:

MeasureSpec.EXACTLY //精确值,设置多少就是多少
MeasureSpec.AT_MOST //至多,实际大小不超过建议值
MeasureSpec.UNSPECIFIED //任意大小,无限大

那这个mode是由什么决定的呢?先从源码解析下,ViewGroup是如何测量child

ViewGroup中测量child调用measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) 即可测量到child

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

此方法中,我们主要分析getChildMeasureSpec如何拿到measureSpec,查看源码

/**
*spec:父View的spec
*padding:父View的内边距
*childDimension:子view设置的大小,例如:30dp、match_parent、wrap_content
**/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //获取父布局模式和大小
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        
        //去掉父布局内边距,得到父布局的实际大小
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // 父布局模式是精确值
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                //设置的值是具体的,所以实际设置多大就是多大,模式就为EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 填充父布局,但是父布局是具体值,所以实际等于父布局设置的大小,模式也为EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 自适应布局,但不能超过父布局的具体值,所以建议值等于父布局设置的大小,模式为AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父布局模式是至多模式
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                //设置的值是具体的,所以实际设置多大就是多大,模式就为EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
              // 填充父布局,父布局为至多,child也为至多,故模式为AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                 //自适应,但不会超过父布局的大小,故模式为AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // // 父布局模式是无限制
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                //设置的值是具体的,所以实际设置多大就是多大,模式就为EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 和父布局同大,模式一样
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //自适应下,父类模式无限制,故自己也不显示,模式为MeasureSpec.UNSPECIFIED
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //返回确定好的child 确定好的spec
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

从源码可以回头看上面的问题mode是由什么决定的呢? 主要由父控件的模式和自身在xml中设置的大小决定

通过measureChild() 测量child,而内部是调用getChildMeasureSpec()拿到了child的spec,里面包含child的大小和模式。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐