自定义View(一)View工作原理之测量 measure

版权声明:本文为博主石月的原创文章,转载请注明出处 https://blog.csdn.net/liuxingrong666/article/details/83280831

在Android中,一个View绘制出来要经过三大流程,分别用measure来测量View的宽高,用layout来确定View在父容器中的位置,最终用draw将View绘制到屏幕上。本章节主要,通过自己的理解来讲解一下第一个流程measure的相关知识点。

measure方法在View类中,在此方法中会 调用onMeasure方法,一般我们通过重写onMeasure方法去自定义View的宽高。ViewGroup类中没有实现onMeasure方法,所以在自定义ViewGroup的时候,我们要重写onMeasure方法,在onMeasure方法中对其所有子元素进行measure过程,通过这种方式将测量行为传递子view,如此类推view的测量行为,从而完成整个View树的测量工作。

讲到view的measure行为,我们首先要一个重要的类MeasureSpec,这个类主要用来保存测量模式SpecMode,规格大小SpecSize,通过一个32位的int值的高2位和低30去保存它们的值。其中SpecMode有三种模式:UNSPECIFUED、EXACTLY、AT_MOST。其中我们主要理解EXACTLY和AT_MOST:

1. EXACTLY 表示父容器已经检测出当前view的大小,就是view在SpecSize中所指定的值,它对应于LayoutParams中的match_parent和具体数值两种模式

2. AT_MOST 表示当前view的大小不超过父容器的可用大小,它对应于LayoutParams中的wrap_content。

接下来我们解决一下ViewGroup的measure过程,虽然ViewGroup没有实现onMeasure方法,但它提供了一些重要的方法用来测量child,先看一下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);
        }
    }
} 

           从上述代码可以看出,ViewGroup会对每一个子元素进行measure,我们再看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);
}

            上面代码中主要是通过调用getChildMeasureSpec方法来获取子元素的测量值,然后通过child的measure方法将测量行为传递下去,我们看看getChildMeasureSpec方法的代码

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: //父容器精确模式
        //子view是精确值
        if (childDimension >= 0) { 
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } 
        // 子view模式MATCH_PARENT,大小为父容器可用的最大空间
        else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } 
        // 子view模式WRAP_CONTENT,大小为父容器可用的最大空间
        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
    //父容器AT_MOST模式
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) { //子view精确值
            // 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);
}

           代码有点长,在这里我们可以清晰地看到,child的MeasureSpec是怎么来的,怎么通过child的layoutparams值和父容器的MeasureSpec去计算得到当前child的MeasureSpec的,有一点要注意的是,默认的情况下,child的layoutparams值不管是match_parent还是wrap_content,其resultSize都是父容器可用空间的大小。

          上面分析了ViewGroup的相关measure行为,主要是对子元素的测量,具体ViewGroup的大小该怎么确定,这个要自定义的时候在onMeasure方法中计算,因为ViewGroup是一个view容器,所以它的宽高是很难有统一的计算方法,不同的情况计算方法又不一样,所以ViewGroup没有去实现onMeasure方法,留给继承者去实现。

           下面我们看一下View类的onMeasure方法,代码如下

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

            可以看到,默认通过getDefaultSize去获取view的测量大小,我们再看看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;
}

            我们只需要看AT_MOST和EXACTLY这两种情况,所以其实此方法返回的就是specSize,而这个值就是view测量后的大小,因为specSize的值在AT_MOST的情形下,是等于父容器可用大小的,而如果在布局中使用wrap_content,specSize大小和match_parent是一样的,所以我们一般在自定义view的时候,对于warp_content的情形,都会给view指定一个自定义的大小,以免和match_parent一样。

            总结一下,通过上面的分析,我们知道view的measure流程大概是怎么的一个过程,即我们首先会在ViewGroup的继承类的onMeasure方法中,要分别去测量父容器的子view,具体怎么通过测量出的child的宽高去计算ViewGroup的宽高,这个需要自己根据具体需求去实现。如果我们要子view中进行自定义的measure行为,就要重写其onMeasure方法,根据需求是实现相关测量逻辑。整个测量流程是从ViewGroup到view的从上到下的行为,从而完成了我们整个view树的测量工作。

猜你喜欢

转载自blog.csdn.net/liuxingrong666/article/details/83280831