android界面的measure详解

想必大家都遇到过在onCreate中调用getMeasuredHeight和getMeasuredHeight这两个函数得到的返回值是0,0的情况吧,这是为什么呢?当然是android界面初始化的相关机制导致的这个“问题”啦,我们现在就来看一下android的view和viewgroup两个类在初始化中measure相关的机制吧。

我们都知道视图的绘制过程要经历三个过程,分别是onMeasure(确定大小),onLayout(确定位置)和onDraw三个方法,我们先从View类中的ViewRootImpl类中开始的,ViewRootImpl对象是view的静态内部类AttachInfo的一个成员变量,上述的绘制过程的三个方法都会在它的performTraversals()中被调用。

那我们就先分步看一下这个函数,由于这个函数比较长,这里就只给出部分代码啦。

      int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
      int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
      //TODO:measureSpec!!!!第一步啊,下一步是OnLayout
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

看一下getRootMeasureSpec方法,那么我们就要先介绍一下View类中的另一个静态内部类MeasureSpec啦,MeasureSpec类封装啦从父节点传递到子节点的布局的需求,每个MeasureSpec既代表了width的需求也代表了height的需求,每个measureSpec有size和mode组成,一共有三种mode:

  1. UNSPECIFIED,代表父节点不对子节点做任何限制,子节点想多大就多大
  2. EXACTLY,代表父节点已经决定了准确的大小,子节点必须遵守
  3. AT_MOST,代表子节点的大小要小于特定的大小,但是具体大小自己决定。
        接下来就看一下getRootMeasureSpec方法

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        //TODO:Match_Parent,Wrap_content和exactly,at_most的对应关系
        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;
    }
其实这里就是将xml布局文件中的match_parent和wrap_content与上边描述的三种mode进行对应,代码上的注释已经说的很清楚啦。然后用mode和size生成一个measureSpec返回。

在performMeasure中其实并没有神马复杂的逻辑,只是调用了对象的一个成员变量mView的measure方法,将childWidthMeasureSpec,childHeightMeasureSpec传递过去,也就是MeasureSpec类中所说的一样,将父节点对子节点的布局要求传递过去。

然后我们就来到了VIew.measure(int,int)这个方法啦,我们可以注意到这是个final方法,也就是说我们不能重载这个方法,在文档中也说明啦,这个方法是为了得出这个view需要多大,而且父节点给出了宽和高的限制信息,但是实际的measure工作是onMeasure方法完成的,子类可以重载这个方法。这就是说明,view的设计师不希望用户重载measure方法,而是重载onMeasure方法。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
这个方法中自由一条语句,我们就先看getSuggestedMinimumWidth()这个方法,后边的height也是一致的,我们就不看了
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
getSuggestedMinimumWidth方法就是比较background的最小宽和view本身的最小宽,返回更小的。

    public static int getDefaultSize(int size, int measureSpec) {
        //TODO:onMeasure真正做事情的地方
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;  //这是原size
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize; //
            break;
        }
        return result;
    }
这里是对MeasureSpec进行解析的地方,在前边我们讲过MeasureSpec只是用来封装信息的类,在这里被解析成specMode和specSize使用。size是view自己决定的大小值,而specMode是父节点对它大小的限制模式,specSize是父节点测定的它的大小。使用switch进行判断,如果是UNSPECIFIED,那么,vie使用自己决定的大小;如果是AT_MOST,不做更改,如果是EXACTLY,那么使用specSize。

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        //TODO: getMeasuredWith==0的原因啊,在这个方法调用之前都是为0的。
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
这个方法在完成之前,mMeasureWidth和mMeasureHeight都是0,所以一般调用getMeasureHeight和getMeasureWidth得到的都是0啦。

对于一个activity来说,界面的初始化要涉及到很多次Measure,每个布局中都有子视图,所以需要迭代的多次measure,下边我们就看一下ViewGroup的measure方法,ViewGroup会调用measureChildren方法,遍历布局下的所有子视图,依次调用measureChild。在这里,会忽略掉GONE的子视图。

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        //这是测量子view的大小的一个方法
        final int size = mChildrenCount; //用于for循环啊
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {  //针对VISIBILITY的view
                measureChild(child, widthMeasureSpec, heightMeasureSpec); //所有的spec都是一样的
            }
        }
    }
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();  //获得view的params

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);//用Spec和viewGroup的paddingleft,right
        //和子view的width来计算
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
在measureChild中,我们会发现方法先获得了子视图的LayoutParams,然后根据父节点的MeasureSpec,左右padding占据的空间,和子视图的大小来获得子视图的MeasureSpec,最后调用子视图的measure()方法。

在getChildMeasureSeec中和RootViewImpl中的getRootMeasureSpec方法类似,只不过是要考虑布局的实际大小,我在代码中有注释,大家可以自己研究。

   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;
        //通过viewGroup的Spec和子view的Spec和设定的w,h来决定子view的size和Spec
        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:  //if is exactly
            if (childDimension >= 0) {  //这是自己设定了大小
                //这是结果的
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {  //values is -1
                // 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) {  //只要是子view设定了specific的值,那么就一定是EXACTLY
                // 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;  //继承viewgroup的
            } 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: //完全让子view决定
            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);
    }

view的measure只是view系统绘制的第一步,在下篇博文中我会分析它的onLayout方法。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值