美文网首页
View/ViewGroup 的测量(measure )流程

View/ViewGroup 的测量(measure )流程

作者: 慕尼黑凌晨四点 | 来源:发表于2020-10-11 15:04 被阅读0次

    View/ViewGroup 的测量(measure )流程

    上文可知,ViewRootImpl.performMeasure中最终是跳到了ViewGroup的Measure方法中来了,我们来看看ViewGroup.Measure()方法。

    ViewGroup的Measure

    ViewGroup没有measure方法。

    image.png

    稍安勿躁,viewGroup继承自view,ViewGroup没有,就看View的。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            boolean optical = isLayoutModeOptical(this);
            if (optical != isLayoutModeOptical(mParent)) {
            ...
            if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                    widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec) {
                ...
                if (cacheIndex < 0 || sIgnoreMeasureCache) {
                    // measure ourselves, this should set the measured dimension flag back
                    onMeasure(widthMeasureSpec, heightMeasureSpec);
                    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                } 
            ...
    }
    

    这里调用到了view的onMeasure()方法。

    • 如果自己是ViewGroup,而ViewGroup又没有默认的onMeasure方法,所以一般继承自ViewGroup的类都会自己重写onMeasure方法。里面必定会遍历自己的子view,并执行child.measure()方法。

    • 如果自己是view的话,测量自己,view.onMeasure()

    为什么要把ViewGroup放在前面,因为布局最顶层的肯定是ViewGroup,最先执行ViewGroup的measureChildren()方法。在这个方法里就确认了其child(子view)的measureSpec。

    所以 等到执行到view.onMeasure(int,int)的时候,该view的measureSpec【出进去的那两个int】已经被确认了。

    ViewGroup的measureChildren

    measureChildren是viewGroup提供的测量子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);
            }
        }
    }
    

    只要状态不是GONE,全用measureChild测量出来;

    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的LayoutParams属性,再用getChildMeasureSpec()方法获取到childMeasureSpec,最后调用child的measure方法。

    关键代码是child的measureSpec是怎么确认的,就在 getChildMeasureSpec()方法里。传入了三个参数:

    1. parent的measureSpec
    2. padding值
    3. view的LayoutParam中设置的width(或height)

    (代码看到了switch case,里面还包了几个if,就知道大概什么情况了,具体解释后面有图)

    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) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
    
        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
    
        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    

    根据 parent的measureSpec自己的layoutparam 的(33)9种情况组合,可以确定下自己的MeasureSpec*。具体如图:

    代码的意思

    如此一来,从顶层的ViewGroup,到最后的View,每个View的MeasureSpec都将被确定下来。

    然后children.measure又调用了measure()方法。

    view的onMeasure()如下:

    首先明白一点,(默认这里的view不是viewGroup)到这里为止的话,MeasureSpec已经确定下来了,下面是根据spec的值确认宽高到底长度是多少。

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

    按照方法执行的顺序,由内到外看吧。

    First,getSuggestedMinimumWidth方法:

        /**
         * Returns the suggested minimum width that the view should use. This
         * returns the maximum of the view's minimum width
         * and the background's minimum width
         *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
         * <p>
         * When being used in {@link #onMeasure(int, int)}, the caller should still
         * ensure the returned width is within the requirements of the parent.
         *
         * @return The suggested minimum width of the view.
         */
    //返回建议的最小值。即【view的最小值 和 background的最小值】中的最大值。
    //当在onMeasure中调用的时候,调用者仍应保证返回值在父级的范围之内。
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    

    解释我放在了注释中。一句话:返回的是系统建议的值。【getSuggestedMinimumHeight同理,一个宽,一个高】

    再看getDefaultSize(),如下:

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

    通过specMode的值,判断到底是用系统建议的值,还是用measureSpec中的值,并将这个值返回。

    最后,最外层setMeasuredDimension()调用这个值 ,作用是用来设置View的宽高的。

    /**
    此方法必须由onMeasure(int, int)被调用,以存储所测量的宽度和测量高度。
    如果不这样做会引发在测量时间的异常。
    **/
    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);
    }
    

    所以一般自己自定义view的时候,沿用这个方法就行了,这个方法传入进去的两个值就是最后的测量结果了。

    通用自定义view

    最后,附上一个通用自定义view的onMeasure部分的代码实现:

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
        }
    
        protected int measure(int measureSpec, boolean WOH) {
            int size = MeasureSpec.getSize(measureSpec);
            int mode = MeasureSpec.getMode(measureSpec);
    
            int measured;
            if (mode == MeasureSpec.EXACTLY) {
                measured = size;
            } else {
                int measureMinimum = WOH ? getMinimumMeasureWidth() : getMinimumMeasureHeight();
    
              // 根据内容计算最小值
              // measureMinimum = Math.max(measureMinimum, MIN_CONTENT_SIZE);
    
                if (WOH) {
                    measureMinimum = Math.max(measureMinimum, measureMinimum + getPaddingLeft() + getPaddingRight());
                } else {
                    measureMinimum = Math.max(measureMinimum, measureMinimum + getPaddingTop() + getPaddingBottom());
                }
                measured = measureMinimum;
                if (mode == MeasureSpec.AT_MOST) {
                    measured = Math.min(measured, size);
                }
            }
            return measured;
        }
    

    测量完成后,下一步,layout。

    相关文章

      网友评论

          本文标题:View/ViewGroup 的测量(measure )流程

          本文链接:https://www.haomeiwen.com/subject/ufsepktx.html