Android View的onMeasure

View的结构

image.png

Android中的View以树的结构来进行管理,从如中可以看到,最顶层的Window到ContentView,这些都是我们在实际开发过程中比较少用到的。

MeasureSpec

MeasureSpec是一个int类型的变量,以下为MeasureSpec的部分源代码:

        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        public static final int AT_MOST     = 2 << MODE_SHIFT;

可以看到MeasureSpec用最高的两位用来存放测量的模式:
UNSPECIFIED:大小不确定的
EXACTLY:大小确定了具体的
AT_MOST:大小的上限,即最大不能超过这个

ViewGroup的测量

View一开始从DecorView开始进行measure,由于DecorView本质是FrameLayout即就是ViewGroup,所以我们一开始从ViewGroup开始讲onMeasure。

ViewGroup从被measure调用之后开始调用onMeasure开始测量自己;
ViewGroup从onMeasure函数中获得的widthMeasureSpec和heightMeasureSpec,根据这两个变量的值来进行对子View的测量。遍历子View更具子View的LayoutParam进行下一步的MeasureSpec计算,即调用getChildMeasureSpec(measureSpec,padding,size);将得到的结果传入到measure中进行子View的onMeasure。

       @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

            int widthSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
            int heightSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);

            super.onMeasure(widthSpec, heightSpec);
            
            for (int i = 0; i < getChildCount(); i++) {
                View view = getChildAt(i);
                LayoutParams lp = view.getLayoutParams();
                int childMeasureWidth = getChildMeasureSpec(widthMeasureSpec,0,lp.width);
                int childMeasureHeight = getChildMeasureSpec(heightMeasureSpec,0,lp.height);
                view.measure(childMeasureWidth,childMeasureHeight);
                //measureChild(view, widthSpec, heightSpec);
            }
        }

这里假设ViewGroup的大小是确定的且为宽高都为500个像素,为什么是500个像素,后面再讲。接着往下走,遍历每一个子View,根据子View的布局参数来计算以下的子View的测量规格即调用了getChildMeasureSpec,接着将处理过的测量规格传给子View进行measure,接着跟踪进measure,measure中代码很长包含了measure缓存的处理比较复杂,这里就不贴出来了,view的measure代码大致上就是调用了View的onMeasure,接着看看View的onMeasure是怎么实现的:

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

可以看到直接通过setMeasuredDimension把size提交上去了,这里的getDefaultSize和getSuggestedMinimumWidth依次:
根据传入的测量规格和建议的默认大小进行实际的大小确定:

    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的背景大小来确定的,如果背景Drawable是空的话那就是默认的mMinHeight的值,该变量默认值是0,否则的话就取背景的最小尺寸

    protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }

通过以上步骤,View的测量就完成了,当然,对于子View中的ViewGroup的测量,递归上述过程就OK了。

补充

measureChild

在View的measure中:

 int childMeasureWidth = getChildMeasureSpec(widthMeasureSpec,0,lp.width);
                int childMeasureHeight = getChildMeasureSpec(heightMeasureSpec,0,lp.height);
                view.measure(childMeasureWidth,childMeasureHeight);

这段代码可以替换成:

measureChild(view, widthSpec, heightSpec);

他俩的实际作用是差不多的,查看系统源码可以一目了然的看到:

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

measureChildren

measureChildren实际上是遍历各个子View,然后分别调用measureChild:

    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);
            }
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容