美文网首页Android
Android中View测量之MeasureSpec

Android中View测量之MeasureSpec

作者: linda_zhou | 来源:发表于2017-08-12 16:43 被阅读119次

    当View测量自身的大小的时候,会执行measure(int widthMeasureSpec, int heightMeasureSpec)方法,至于measure方法内容怎么执行的,这里先不去探讨。注意方法中两个参数,它们其实是一个int 类型的MeasureSpec。MeasureSpec可以说是View测量过程的前提,所以我们很有必要先来了解一下MeasureSpec。

    MeasureSpec 工作原理

    MeasureSpec 代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。
    SpecMode是指测量模式,SpecSize是指在某种测量模式下的大小。

    MeasureSpec是View中的一个静态内部类。

    public static class MeasureSpec {
    
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        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理解为测量规则,而这个测量规则是由测量模式和和该模式下的测量大小共同组成的。
    int MeasureSpec = MeasureSpec.makeMeasureSpec(specSize,SpecMode);
    
    • 确定了View测量规则后,我们也可以通过测量规则获取测量模式和该模式下的测量大小
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    

    SpecMode有三类:

    1. UNSPECIFIED
      父容器不对View有任何限制,要多大给多大,这般情况一般用于系统内部,表示一种测量状态,如ScrollView测量子View时用的就是这个。
    2. EXACTLY
      父容器已经检测出View所需要的大小,这个时候View的最终大小就是SpecSize所测定的值,它对应于LayoutParams中的match_parent和具体的数值(如40dp,60dp)这两种模式。
    3. AT_MOST
      父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content.

    普通View的MeasureSpec的创建过程

    MeasureSpec很重要,上文中我们也了解了MeasureSpec的工作原理,那如何获取MeasureSpec呢?下面就结合源码来分析MeasureSpec的创建过程。

    先来看下ViewGroup中的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的布局参数,然后通过getChildMeasureSpec方法分别得到子View的宽高测量规则,即childWidthMeasureSpec和childHeightMeasureSpec,最后调用子View的measure方法,至此测量过程就由父View传递到了子View.。MeasureSpec确定后就可以在onMeasure方法确定View的测量宽高了。

    我们重点分析的是getChildMeasureSpec方法,源码如下:

        public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);  //返回父View的测量模式
            int specSize = MeasureSpec.getSize(spec);  //返回父View的测量大小
    
            int size = Math.max(0, specSize - padding);  //父View的测量大小 - 父View的padding占用的大小,剩余的即是子View可用的最大空间
    
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {  //子View大小为具体数值的情况
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {   //子View大小为match_parent的情况
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {   //子View大小为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);
        }
    

    此方法比较清晰,它主要用来通过父View的MeasureSpec和子View的LayoutParams来确定子View的MeasureSpec的,即普通View的MeasureSpec创建过程。

    为了让猿宝宝们更清晰的理解getChildMeasureSpec方法,借用《Android开发艺术探索》中一张表格,如下:

    MeasureSpec创建过程

    DecorView的MeasureSpec创建过程

    普通View的MeasureSpec的创建过程阐述了怎样通过父View的MeasureSpec和子View的LayoutParams来确定子View的MeasureSpec。那顶级View,即DecorView的MeasureSpec创建过程又是怎样的呢?ViewRootImp的measureHierarchy方法中有如下代码:

        childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    

    接着来看getRootMeasureSpec方法

        private static int getRootMeasureSpec(int windowSize, int rootDimension) {
            int measureSpec;
            switch (rootDimension) {
    
            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;
        }
    

    从上述源码,我们可以得出如下规则,具体根据它的LayoutParams来划分:

    • LayoutParams.MATCH_PARENT:精确模式 其大小就为屏幕的尺寸大小
    • ViewGroup.LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过屏幕的大小
    • 具体数值(如40dp):精确模式,大小为LayoutParamas指定的大小。

    至此,相信大家对MeasureSpec有一定了解了,当我们确定了View的MeasureSpec后,我们怎么用它来测量View的大小呢?具体请看View工作原理之measure过程解析

    相关文章

      网友评论

        本文标题:Android中View测量之MeasureSpec

        本文链接:https://www.haomeiwen.com/subject/hqhjrxtx.html