View的测量过程学习onMeasure

首先来说下一个int值,它被定义在VIew内部类里。叫MeasureSpec, 俗称测量规格。

        int mode =MeasureSpec.getMode(widthMeasureSpec);
        int size =MeasureSpec.getSize(widthMeasureSpec);

高2位代表一个SpecMode. 表示测量模式,低30位代表SpecSize.表示测量大小,分别可以通到以上方法取出。

当然也可以生成

int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

Mode有3种,

EXACTLY      父容器已经检测出子View的精确大小,在该模式下View的测量大小为SpecSize.               精准模式

AT_MOST    父容器未能检测出子View的大小,但可以给定一个最大值,子View测量大小不大于这个值。    至多模式

UNSPECIFIED.  父容器不对子View的大小做限制。     不限制模式

对于不同的类型,测量过程分为几种,一般来说,一个View的宽高是由自身的layoutParams和父容器的MeasureSpec一起决定的。

1. 顶级View  DecorView

对于顶级View,它的宽由屏幕宽度和自身layoutparams的宽度决定,高也是如此,其实和一般的View差不多。 

2.ViewRootImpl

3.ViewGroup    

    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);
            }
        }
    }

代码里可以看出,对于子View,如果不隐藏,则会根据父View的测量规格计算出子View的。

来看一下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的MeasureSpec,然后调用子View的measure方法。上面第二个参数只传了这个ViewGroup的内边距,还有一种情况是带有外边距参数的测量,如下

    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);
    }

重点看getChildMeasureSpec,该方法根据父容器的MeasureSpec和子View的layoutParams生成了子View的MeasureSpec.

    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);
    }

第一步: 
得到父容器的specMode和specSize,请参见第2-3行代码。

第二步: 
得到父容器在水平方向或垂直方向可用的最大空间值,请参见第5行代码。

第三步: 
确定子View的specMode和specSize,请参见第10-50行代码。 

来看下switch语句,根据父容器的模式来分别处理

一: 精准模式下exactly,去判断layoutParams的参数,有3种情况 >0,-1,-2.

  /**
         * Special value for the height or width requested by a View.
         * MATCH_PARENT means that the view wants to be as big as its parent,
         * minus the parent's padding, if any. Introduced in API Level 8.
         */
        public static final int MATCH_PARENT = -1;

        /**
         * Special value for the height or width requested by a View.
         * WRAP_CONTENT means that the view wants to be just large enough to fit
         * its own internal content, taking its own padding into account.
         */
        public static final int WRAP_CONTENT = -2;
1.大于0,代表我们的layoutParams里定义了具体的像素。所以测量的子View宽或者高是精准模式,大小 =childDimension

2.MATCH_PARENT下,子View想要填满父View,所以子View也是精准模式,且大小和父View剩余空间一样 =size 

3.WARP_CONTENT下,子View想要自己定义大小,但不能大于父View的大小,所以模式是至多模式,大小 =size

二: 至多模式下at_most

1.大于0,子View是精准模式,大小为 =childDimension

2.MATCH_PARENT下,子View想要填满父View,但父View也不是具体多大,所以认作不超过父View,模式是至多模式,大小= size

3.warp_content下,也是一样的至多模式,大小 =size 

三: 不限制模式 UNSPECIFIED

1.大于0,子View是精准模式,大小为 =childDimension

2.MATCH_PARENT,子View是是不限制的,所以是不限制模式,且大小为0.

3.同2上


上面的各个子View的measure方法中都会调用到onMeasure方法, 我们就重点看这个方法。这个方法是View的,

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
set方法表示设置宽高,我们重点看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;
    }
看代码,第一个参数其实只在不限制的模式下才用,对于其他2个模式,都是取传入的measurespec里面的大小。看一下getSuggestedMinimumWidth方法,表示取的是背景和minWidth中的较大值,我们可能在listView里设置过minWidth这个属性,因为他就是使用的是不限制模式。这里就不深入了


还有一个结论。

直接继承View类的自定义控件需要重写onMeasure方法并设置warp_content时的自身大小。因为在至多模式下,系统默认取了最大的那个值,也就是父容器所剩余的空间值,和match一样大,而在ImageView 和TextView里都有特殊处理。故在自定义View继承View的情况下用到wrap需要做一些处理,如下;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        /*
         * this.getLeft(); //获取与父容器左边距离 this.getRight(); //获取与父容器右边距离 int width
         * = this.getRight()-this.getLeft();
         */
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        if(modeWidth==MeasureSpec.AT_MOST&&modeHeight==MeasureSpec.AT_MOST) {
            setMeasuredDimension(100, 100);
        }
        else  if (modeWidth==MeasureSpec.AT_MOST) {
            setMeasuredDimension(100, sizeHeight);
        }
        else if(modeWidth ==MeasureSpec.AT_MOST) {
            setMeasuredDimension(modeWidth, 100);
        }          
       // int measureSpec = MeasureSpec.makeMeasureSpec(size, mode);
    }



考虑到ViewGroup里嵌套这ViewGroup,所以还得看下ViewGroup的onMeasure方法,但它是一个抽象类,它的onMeasure方法都在子类里实现,考虑到不同的布局特性,比如一些weight等特殊性,。

列举一下

1.线性布局 

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








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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值