Measure(0,0)的探讨

最近研究自定义view,遇到一个问题,在自定义ViewGroup的时候经常会在父view的onMeasure方法中调用如下代码去测量子view的宽高:
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //测量ViewGroup本身的宽高,由于没有wrap_content的需求,所以直接用父view的没问题
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //测量第一个孩子
        firstView = getChildAt(0);

        //测量第一个孩子的宽高
        //方式一:
        firstView.measure(0, 0);
        //方式二:
        firstView.measure(300, heightMeasureSpec);
        //方式三:
        firstView.measure(MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY));

        firstView.measure(1073742124, heightMeasureSpec);
        firstView.measure(-2147483648 + 300, heightMeasureSpec);
    }
下面对几种方式做详细说明以及自我理解(不保证完全正确...自己写了Demo测试过,应该没错):
当然,首先要对view的绘制流程measure(),layout(),draw()比较了解,MeasureSpec以及三种测量模式熟悉

推荐两篇写的不错的博客:

http://blog.csdn.net/iispring/article/details/49403315
http://blog.csdn.net/lfdfhl/article/details/51347818
下面代码都是在系统默认的实现下进行,没有进行重写!!!

方式一:firstView.measure(0, 0);

        其实在measure()方法中最终会回调onMeasure(int widthMeasureSpec, int heightMeasureSpec),所以相当于

        widthMeasureSpec = 0;
        heightMeasureSpec = 0;

        所以当用下面方法获取父view传递的测量模式和测量大小时:

        childMode = MeasureSpec.getMode(0) = 0;    //获取widthMeasureSpec的最高2位,通过位运算
        childWidth = MeasureSpec.getSize(0) = 0;   //获取widthMeasureSpec的低30位,通过位运算

        当childMode = 0 的时候,也就子view的宽度测量模式是MeasureSpec.UNSPECIFIED,
        意思就是父view不限制子view的宽高,子view想要多大就多大,也就是完全wrap_content的效果(没有父view大小的限制),
        所以子view布局文件中写的多少宽高,这个并不生效,而是要看具体子view的wrap_content实现(没有父view大小的限制的情况下)

    方式二:firstView.measure(300, heightMeasureSpec);

        对于宽度:因为传入的是300,由于300转换为32位(int类型是32位)二进制之后,最高2位仍然是00,
        所以childMode仍然为0,所以子view的宽度测量模式是MeasureSpec.UNSPECIFIED
        最终测量出来的宽度也是子view的wrap_content(没有父view大小的限制)的大小,并不是300

        对于高度:因为传入的是父view生成的heightMeasureSpec,所以会根据这个值生成对应的高度
        MeasureSpec.UNSPECIFIED;最终高度为子view的wrap_content(没有父view大小的限制)的大小
        MeasureSpec.EXACTLY;最终高度为MeasureSpec.getSize(heightMeasureSpec)的值
        MeasureSpec.AT_MOST:最终高度不超过MeasureSpec.getSize(heightMeasureSpec)的     
                            值,具体要看子view onMeasure()的实现(有父view大小的限制)

    方式三:firstView.measure(MeasureSpec.makeMeasureSpec(300,MeasureSpec.EXACTLY),
                            MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY));
        由于子view宽高的测量规格是通过MeasureSpec.makeMeasureSpec()生成的,所以32位的前
        两位会是对应的测量模式,子view会在onMeasure()方法中根据大小和模式生成最终的值


总结:假设我们在布局文件中写的宽和高的值都是是500,但是子view的wrap_content的测量宽高大小是800,最终结果如下:
        方式一:childWidth = childHeight = 800;
        方式二:childWidth = 800;  
              childHeight = 500;
        方式三:childWidth = childHeight = 300;

    由于Android系统中,下面三个变量转换成十进制之后的值如下所示:
        MeasureSpec.UNSPECIFIED = 0;
        MeasureSpec.EXACTLY = 1073741824;
        MeasureSpec.AT_MOST = -2147483648;

      所以在measure()中传入的值X分类如下:
      当x > 1073741824的时候,测量模式为MeasureSpec.EXACTLY,测量大小是(X-1073741824);0<X<1073741824的时候,测量模式为MeasureSpec.UNSPECIFIED,测量大小是wrap_content具体控件的实现高度;
      当-2147483648<X<0的时候,测量模式为MeasureSpec.AT_MOST,测量大小是不能超过(|X| - 2147483648);x < -2147483648的时候,测量模式为MeasureSpec.UNSPECIFIED,测量大小是wrap_content具体控件的实现高度(并且没有父view大小的限制);

里面有很多细节的东西没有讲,讲起来牵扯的东西太多,作为自己的笔记吧!有问题的可以指出和一起探讨!

下面附上在系统ViewGroup中用于生成子view的MeasureSpec的源码(非常重要):

    在measureChildren()中遍历子view并调用measureChildWithMargins()方法,
    在measureChildWithMargins()方法中调用getChildMeasureSpec()生成子view的measureSpec,并测量子view
/**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding
     * and margins. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    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);
    }
/**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @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) {
        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 = 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;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值