View/ViewGroup 的测量(measure )流程

2020-10-11  本文已影响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,最先执行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。

上一篇下一篇

猜你喜欢

热点阅读