View的measure过程

View的measure是View的三大过程最复杂的一个。View的measure过程要分情况来看,如果只是一个原始的View,那么通过measure方法就完成了测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归执行这个过程,下面分两种情况分别讨论。

0、MeasureSpec

在说measure过程之前,需要先说明一个类,MeasureSpec,从名字来看,MeasureSpec看起来像测量规格或者测量说明书,不管怎么翻译,它看起来都反应了一个View的测量过程,通过源码发现,MeasureSpec确实参与了View的measure过程。

MeasureSpec在很大程度上决定了一个View的尺寸规格,这句是核心,那么问题又来了,为什么是很大程度呢,是因为MeasureSpec的创建还和父容器有关,系统会根据View的LayoutParams根据父容器的MeasureSpec规格转换成对应的MeasureSpec。

MeasureSpec代表一个32位int值,高2位为SpecMode,低30位代表SpecSize。

SpecMode指测量模式,而SpecSize是指在某种测量模式下的规格大小。

SpecMode有三类:

EXACTLY

父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize的值,对应于LayoutParams.MATCH_PARENT和具体数值两个模式

AT_MOST

父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么要看不同View的具体实现,对应于LayoutParams.WRAP_CONTENT

UNSPECIFIED

父容器不对View有任何限制,要多大给多大,一般用于系统内部。

MeasureSpec的创建还和父容器有关,系统会根据View的LayoutParams根据父容器的MeasureSpec规格转换成对应的MeasureSpec。在源码中,通过一个方法getChildMeasureSpec()就可以看出来。而这个方法在ViewGroup的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方法截图

* @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension)
通过上面两段源码可以看出,getChildMeasureSpec方法中各个参数的含义为:spec为父容器的 MeasureSpec,padding就是布局中padding,childDimension为子View的LayoutParams。

LayoutParams的参数有:

LayoutParams.MATCH_PARENT;LayoutParams.WRAP_CONTENT;还有就是固定大小了,LayoutParams在布局文件里就已经申明了。

getChildMeasureSpec清楚展示了普通View的MeasureSpec的创建规则,如下图所示



1、View的measure过程

view的measure过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View的measure中回去调用onMeasure方法,因此只要看onMeasure方法即可,代码如下。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
上述代码很简洁,但是间接并不代表简单,setMeasuredDimension方法会设置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;
    }

由于UNSPECIFIED用于系统内部,所以暂时不管,针对AT_MOST,EXACTLY两个,此时直接返回specSize,就是View测量后的大小。从 getDefaultSize这个方法来看,系统并没有处理wrap_content这个属性。而由MeasureSpec的创建可知,当出现wrap_content就是match_content。所以我们在自定义View时,如果直接继承自View,需要在onMeasure方法中对wrap_content这个属性做一下处理,比如在TextView中,它是直继承自View的,所有在他的onMeasure方法中找到了一句

if (widthMode == MeasureSpec.AT_MOST) {
    width = Math.min(widthSize, width);
 }

其中withSize就是SpecSize,with是其实际所占大小。源码这一块写的特别长,不赘述。


2、ViewGroup的measure过程

对于ViewGroup来说,处理完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个过程,和View不同的是,ViewGoup是一个抽象类,因此它没有重写View的onMeasure方法,但是提供了一个measureChild的方法。

那么ViewGoup是如何完成自己的measure的过程呢。由于ViewGoup是一个抽象类,需要被继承,所以它的measure需要由其子类去实现,也就是LinearLayout,或者RelativeLayout来实现,完成自己的measure过程又需要父容器的MeasureSpec,ViewGroup的父容器有可能还是ViewGroup或者DecorView,那么再往上追究,DecorView的测量又是怎么完成的呢?

对于DecorView来说,在ViewRootImpl中的measureHierarchy方法中有如下一段代码,它展示了DecorView的MeasureSpec的创建过程,desiredWindowHeight,childWidthMeasureSpec为屏幕的尺寸。为什么DecorView的MeasureSpec会在这里被创建呢,因为DecorView是放在PhoneWindow上的,这个过程PhoneWindow通过ViewRootImpl来实现。

                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
接着看getRootMeasureSpec方法,
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

通过上述代码,DecorView的MeasureSpec的产生就明确了,那么ViewGroup的MeasureSpec的产生也就明确了。

ViewGroup产生了自身的MeasureSpec之后,开始测量每一个子View。通过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 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);
            }
        }
    }


这段代码也比较好理解,measureChild就是取出子元素的LayoutParams,然后再通过getChilMeasure方法来创建子元素的MeasureSpec,接着讲MeasureSpec直接传递给子元素的measure方法进行测量。 getChilMeasure在之前也说明过了。


ViewGroup是一个抽象类,所以它并没有完成测量的全过程,它的onMeasure方法需要它的子类去完成,但是ViewGroup里面的方法是其子类所公共的。相当于基类。那么为什么ViewGroup不一起实现onMeasure呢,因为它的子类比如LinearLayout或者RelativeLayout他们的onMeasure方法差别很大,所以不好统一实现,还不如让子类自己去实现。

下面以LinearLayout总结ViewGroup子类的onMeasure方法是如何实现的。

首先看LinearLayout的onMeasure方法,源码如下所示:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

上述比较易懂,意思是当布局采用垂直时,使用measureVertical方法,当布局采用水平时,采用measureHorizontal方法。再以 measureVertical方法为例。measureVertical的源码特别长,这里只取一段代码,分析其大概逻辑。

 // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) { //之间省略N行
<span style="white-space:pre">		</span>// Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);

                if (oldHeight != Integer.MIN_VALUE) {
                   lp.height = oldHeight;
                }

                final int childHeight = child.getMeasuredHeight();
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));//下面省略N行
}

从上面这段代码可以看出,系统会遍历子元素并对每个子元素执行measureChildBeforeLayout方法,这个方法内部会调用子元素的measure方法,这样子元素就开始进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方法的初步高度。每遍历一个子元素,mTotalLength就会增加,增加的部分主要包括子元素的高度以及子元素在竖直方向上的margin等。当子元素测量完毕后,LinearLayout会测量自己的大小。源码如下,该段依然在 measureVertical这个方法中。最后调用setMeasuredDimension这个方法设置LinearLayout的宽高。onMeasure方法也就执行完了,垂直方向的LinearLayout测量完毕。

<span style="white-space:pre">	</span>// Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        
        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

	//。。。。。之间省略N行

	setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
               	 	heightSizeAndState);







  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值