美文网首页工作生活
Android View 框架(2)-- measure

Android View 框架(2)-- measure

作者: 黑色偏幽默 | 来源:发表于2019-07-02 16:32 被阅读0次

    本篇你将了解到:

    1. LayoutParamsMeasureSpec 的作用和使用场景
    2. 父 View 如何使用 MeasureSpec 影响子 View 的测量
    3. 重写 onMeasure 的作用

    View 的绘制过程,主要体现在 onMeasure()onLayout()onDraw() 这三个方法,这三部对应着一个 View 的测量,布局,绘制三个步骤。

    在讲 onMeasure() 方法之前,我们先讲一下 Android 中,测绘中常用的参数类 -- LayoutParamsMeasureSpec

    LayoutParams

    LayoutParams 类只简单的描述了宽高,他们往往被设置成以下这三种值:

    1. 一个确定的值;
    2. FILL_PARENT,即填满父容器;
    3. WRAP_CONTENT,刚刚好包裹组件;

    常见的使用方式如下:

    
    // FrameLayout 的子 View 
    FrameLayout.LayoutParams lytp = new FrameLayout.LayoutParams(80, LayoutParams.WRAP_CONTENT);
    lytp .gravity = Gravity.CENTER;
    btn.setLayoutParams(lytp);
    
    
    // RelativeLayout 的子 View
    RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 
    lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); 
    lp.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); 
    btn1.setLayoutParams(lp);
    

    MeasureSpec

    MeasureSpec 是父控件提供给子 View 的一个参数,作为设定自身大小参考,而子 View 也可以完全不参照这个值,设定自己的大小。

    MeasureSpecsizemode 构成,size 代表着子 View 当前所在父布局的具体尺寸,其中 mode 包括以下三种:

    1. EXACTLY:父布局为子 View 指定确切大小,希望子 View 完全按照自己给定尺寸来处理。
    2. AT_MOST:父布局为子元素指定最大参考尺寸,希望子 View 的尺寸不要超过这个尺寸。
    3. UNSPECIFIED:父布局对子控件不加任何束缚,这种情况一般出现在 ScrollView 这种不限制大小的特殊父控件。

    ViewGroup 如何测量子 View

    看源码:

    // 测量子 View
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        // 获取 child 的 LayoutParams
        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
     * @param spec 子 View MeasureSpec
     * @param padding 当前 View 的 padding 大小,需要被减去
     * @param childDimension 子 View LayoutParams 的height,width
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        // 父 View 推荐的 Size 需要留有一部给 padding
        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;
            ...
            // 省略了 MeasureSpec.AT_MOST 和 MeasureSpec.UNSPECIFIED
        }
    }
    

    看完源码后,大致能了解到测量是由父 View 的 MeasureSpec 和子 View 的 LayoutParams 一起决定的。更具体的对应关系如下:

    1. 子 View 指定大小值时:

      • Mode = EXACTLY
      • Size = 指定的大小
    2. 子View指定为 MATCH_PARENT 时:

      • Mode = 父View此时的模式
      • Size = 父View的大小 – padding
    3. 子View指定为 WRAP_CONTENT 时:

      • Mode = AT_MOST
      • Size = 父View的大小 – padding

    根据以上的规律,父 View 一层一层的向下设置子 View 的 MeasureSpec。那么子 View 是如何根据父 View 给的 MeasureSpec,确定自身的大小呢?答案就在下面马上要讲的 onMeasure()方法里。

    为什么要重写 onMeasure ?

    调用 setMeasuredDimension 后,正式的确定了当前 View 的大小,那默认的onMeasure是怎么去调用的呢?

     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
        
    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;
        // AT_MOST 和 EXACTLY 使用同一种处理方式
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
    

    View 的默认 onMeasure 方法中,可以看到 MeasureSpec.AT_MOSTMeasureSpec.EXACTLY 方法处理的方式是一样的。这样导致,自定义的 View 使用 WRAP_CONTENTMATCH_PARENT 的效果是一样的,都会撑满整个父容器。

    在某些情况下,我们并不想要默认的那种效果,这时候我们就可以根据自己的需求,去重写 onMeasure 方法。
    这里我们简单的重写一下,实现功能如下:

    1. 如果指定高度和宽度的大小,那么结果就是这个值;
    2. 如果设置为 MATCH_PARENT ,就填满父容器;
    3. 如果设置为 WRAP_CONTENT ,就在父容器建议值和本 View 设置的建议值取最小值。
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(doLocalMeasure(60, widthMeasureSpec),
                doLocalMeasure(30, heightMeasureSpec));
    }
    
    int doLocalMeasure(int defaultSize, int measureSpec) {
        int result = defaultSize;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.EXACTLY:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
                result = Math.min(result, size);
                break;
        }
        return result;
    }
    

    小结

    这一章简单的讲了 View 的测量流程,以及重写 onMeasure 方法的作用。
    下一篇:Android View 框架(3)-- layout

    相关文章

      网友评论

        本文标题:Android View 框架(2)-- measure

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