Android中mesure过程详解

invalidate()最后会发起一个View树遍历的请求,并经过执行performTraersal()来响应该请 求,performTraersal()正是对View树进行遍历和绘制的核心函数,内部的主体逻辑是判断是否须要从新测量视图大小(measure), 是否须要从新布局(layout),是否从新须要绘制(draw)。measure过程是遍历的前提,只有measure后才能进行布局(layout) 和绘制(draw),由于在layout的过程当中须要用到measure过程当中计算获得的每一个View的测量大小,而draw过程须要layout肯定每 个view的位置才能进行绘制。下面咱们主要来探讨一下measure的主要过程,相对与layout和draw,measure过程理解起来比较困难。程序员

      咱们在编写layout的xml文件时会碰到layout_width和layout_height两个属性,对于这两个属性咱们有三种选择:赋值成具体 的数值,match_parent或者wrap_content,而measure过程就是用来处理match_parent或者 wrap_content,假如layout中规定全部View的layout_width和layout_height必须赋值成具体的数值,那么 measure实际上是没有必要的,可是google在设计Android的时候考虑加入match_parent或者wrap_content确定是有原 因的,它们会使得布局更加灵活。函数

      首先咱们来看几个关键的函数和参数:布局

      一、public final void measue(int widthMeasureSpec, int heightMeasureSpec);this

      二、protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);google

      三、protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)spa

      四、protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)设计

      五、protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)code

     接着咱们来看View类中measure和onMeasure函数的源码:orm

    

复制代码

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {            // first clears the measured dimension flag
            mPrivateFlags &= ~MEASURED_DIMENSION_SET;            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
            }            // measure ourselves, this should set the measured dimension flag back            onMeasure(widthMeasureSpec, heightMeasureSpec);            // flag not set, setMeasuredDimension() was not invoked, we raise            // an exception to warn the developer
            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    }

复制代码

 

因为函数原型中有final字段,那么measure根本没打算被子类继承,也就是说measure的过程是固定的,而measure中调用了onMeasure函数,所以真正有变数的是onMeasure函数,onMeasure的默认实现很简单,源码以下:xml

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

onMeasure默认的实现仅仅调用了setMeasuredDimension,setMeasuredDimension函数是一个很关键的函数,它对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值,而measure的主要目的就是对View树中的每一个View的mMeasuredWidth和mMeasuredHeight进行赋值,一旦这两个变量被赋值,则意味着该View的测量工做结束。

 

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= MEASURED_DIMENSION_SET;
    }

 

对于非ViewGroup的View而言,经过调用上面默认的measure——>onMeasure,便可完成View的测量,固然你也可 以重载onMeasure,并调用setMeasuredDimension来设置任意大小的布局,但通常不这么作,由于这种作法太“专政”,至于为什么“专政”,读完本文就会明白

      对于ViewGroup的子类而言,每每会重载onMeasure函数负责其children的measure工做,重载时不要忘记调用 setMeasuredDimension来设置自身的mMeasuredWidth和mMeasuredHeight。若是咱们在layout的时候不 须要依赖子视图的大小,那么不重载onMeasure也能够,可是必须重载onLayout来安排子视图的位置,这在下一篇博客中会介绍。  

      再来看下measue(int widthMeasureSpec, int heightMeasureSpec)中的两个参数, 这两个参数分别是父视图提供的测量规格,当父视图调用子视图的measure函数对子视图进行测量时,会传入这两个参数,经过这两个参数以及子视图自己的 LayoutParams来共同决定子视图的测量规格,在ViewGroup的measureChildWithMargins函数中体现了这个过程,稍后会介绍。

     MeasureSpec参数的值为int型,分为高32位和低16为,高32位保存的是specMode,低16位表示specSize,specMode分三种:

      一、MeasureSpec.UNSPECIFIED,父视图不对子视图施加任何限制,子视图能够获得任意想要的大小;

      二、MeasureSpec.EXACTLY,父视图但愿子视图的大小是specSize中指定的大小;

      三、MeasureSpec.AT_MOST,子视图的大小最可能是specSize中的大小。

      以上施加的限制只是父视图“但愿”子视图的大小按MeasureSpec中描述的那样,可是子视图的具体大小取决于多方面的。

      ViewGroup中定义了measureChildren, measureChild,  measureChildWithMargins来对子视图进行测量,measureChildren内部只是循环调用 measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也 做为子视图的大小,咱们主要分析measureChildWithMargins的执行过程:

 

复制代码

    protected void measureChildWithMargins(View child,            int parentWidthMeasureSpec, int widthUsed,            int parentHeightMeasureSpec, int heightUsed) {        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                        + widthUsed, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

复制代码

 

总的来看该函数就是对父视图提供的measureSpec参数进行了调整(结合自身的LayoutParams参数),而后再来调用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:            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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }            break;
        }        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

复制代码

getChildMeasureSpec的整体思路就是经过其父视图提供的MeasureSpec参数获得specMode和specSize,并 根据计算出来的specMode以及子视图的childDimension(layout_width和layout_height中定义的)来计算自身 的measureSpec,若是其自己包含子视图,则计算出来的measureSpec将做为调用其子视图measure函数的参数,同时也做为自身调用 setMeasuredDimension的参数,若是其不包含子视图则默认状况下最终会调用onMeasure的默认实现,并最终调用到 setMeasuredDimension,而该函数的参数正是这里计算出来的。

 

      总结:从上面的描述看出,决定权最大的就是View的设计者,由于设计者能够经过调用setMeasuredDimension决定视图的最终大小,例如 调用setMeasuredDimension(100, 100)将视图的mMeasuredWidth和mMeasuredHeight设置为100,100,那么父视图提供的大小以及程序员在xml中设置的 layout_width和layout_height将彻底不起做用,固然良好的设计通常会根据子视图的measureSpec来设置 mMeasuredWidth和mMeasuredHeight的大小,已尊重程序员的意图。

相关文章
相关标签/搜索