Android View工作原理(一)----子View的measure(即子View的尺寸确定)

我们都知道android的所有View都是在自己的onMeasure()中确定自己的尺寸(即width和height),接下来我们就看一看是如何实现。

1. 什么是MeasureSpec?

android官方文档是这样描述的:A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. [1]

也就是说:MeasureSpec 封装了父View对子View 的布局要求(其实就是对子View的长和宽的要求),并且同时MeasureSpec 由尺寸(size)和模式( mode)组成。

源码分析

        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
	public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
	public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

MeasureSpec代表一个32位的int值, 其中高2位代表SpecMode,而低30位代表SpecSize

MeasureSpec共有UNSPECIFIED,EXACTLY,AT_MOST三种模式。

UNSPECIFIED 父View对子View的并没确切的布局限制,这种情况一般用于系统内部。[2]

EXACTLY 父View对子View有精确的布局要求,这个时候这个时候View的大小就是MeasureSpec所指定的值,它对应于LayoutParams中具体的数值(比如666dp)和match_parent[2]

AT_MOST 父View指定了一个最大可用的大小,子View不能超过这个限制。它对应于LayoutParams中的wrap_content[2]


2. MeasureSpec 和LayoutParams的关系(子View的MeasureSpec的产生)

从上面我可以看出父View通过MeasureSpec对子View进行大小的要求,这也只是要求,而并不是完完全全的按照父View的MeasureSpec来设置。那子View的MeasureSpec如何确定呢?除了父View的MeasureSpec对子View的要求,还有我们自己对子View设置的LayoutParam,这两者共同作用,共同决定子View的MeasureSpec。

(MeasureSpec)

为了证实上面的说法,我们可以看看ViewGroup如何得到子View的MeasureSpec(注:这是方法之一)

    /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    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的大小是由父View的MeasureSpec 和 子View的MeasureSpec共同作用的结果。

而其中getChildMeasureSpec()则是产生子ViewMeasureSpec的规则。

源码:

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);
    }
上述代码很容易看出规则,我们总结一张表:

[3]

总结(由于UNSPECIFIED是用作系统的,所以用户可以不加考虑):

1) 只要子View的LayoutParam是精确值,不管父View是什么mode,得到的子View 的 MeasureSpec的 mode 都是 Exactly

2) 只要子View的LayoutParams是wrap_content,则得到的子View的MeasureSpec的mode是AT_MOST且大小不能超过parentSize。因此我们在子View的onMeasure()方法中要注意我们大小的设定,如果不自我调整的话,则默认为最大值(即parentSize)

3) 对于子View的LayoutParams是match_parent,则情况会有不同,子View的在onMeasure()方法中根据自己的View特性,稍加注意

4)对于得到的子View的MeasureSpec的mode是EXACTLY,除非你的子View很特别,否则子View的最终大小都要是得到的MeasureSpec中的size


3. 子View的大小确定

(注意:我们这次没有说是子View的MeasureSpec)

在子View的onMeasure()方法中,最终确定我们子View的长和宽。

可以根据子View的MeaSpe,子View的LayoutParams和子View本身的特性,共同决定我们子View的大小(即width和height)

4. 理解流程图


至此,感谢文章中参考的那些人们,如果有遗漏,请留言。如果有错误的地方多多指点

参考:

[1] https://developer.android.com/reference/android/view/View.MeasureSpec.html

[2] 任玉刚 《Android开发艺术探索》第4章  “View的工作原理” p178

[3] 任玉刚 《Android开发艺术探索》第4章  “View的工作原理” p182

转载,请注明出处

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值