View的测量

今天讲的是View的测量,这里就涉及到一个变量MeasureSpec;该变量google开发人员将其设计得很巧妙;里面其实装了两个值:Mode和Size;需要了解他们之间怎么转换可以看看我之前写的 View静态类MeasureSpec
入正题的View的测量当然看它的onMeasure()方法

onMeasure()

View的测量_第1张图片
onMeasure

源码就是再调用一个setMeasuredDimension()方法;
先看看源码对onMeasure该方法的解释:

Measure the view and its content to determine the measured width and the
measured height. This method is invoked by {@link #measure(int, int)} and
should be overridden by subclasses to provide accurate and efficient
measurement of their contents.

大概意思:测量该View和它的内容决定这测量的宽高。该方法被measure()方法调用和继承它的子类需要重写该方法以提供测量其内容。那就是说一般我们的自定义View都需要重写此方法。继续看源码:

setMeasuredDimension()

This method must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.

大概意思:该方法一定要被onMeasure()调用去存储测量后的宽高。不这样做就会在测量时间上出现异常。

所以我们的自定义View最终调用setMeasureDimension()即可,无需关心它里面干什么。

getDefaultSize(int size, int measureSpec)

这个就是setMeasureDimension方法传入的参数。看看干什么的:

View的测量_第2张图片
getDefaultSize

这里简单解释一下:

  • MeasureSpec.UNSPECIFIED 为父类不确定模式,大小由子类View确定
  • MeasureSpec.AT_MOST 为最大化模式,即在这个撑满这个最大值
  • MeasureSpec.EXACTLY 为精确模式,即平时具体设定多少Dp,Sp或MATCH_PARENT都是基于这个模式;
    以上三种模式是我基于写代码看源码时的自己理解,具体你可以看下面官方的解释:
View的测量_第3张图片
Paste_Image.png
View的测量_第4张图片
Paste_Image.png
View的测量_第5张图片
Paste_Image.png

回来解释一下getDefaultSize方法,其实就是作了一个十分简单的测量:当UNSPECIFIED模式时,取传入来的默认值,即源码传入的0;
当AT_MOST和EXACTLY模式,获取measureSpec的size值。这种情况下,会出现什么问题呢?
就是如果我们的自定义View不重写onMeasure方法时候;

在布局文件写WRAP_CONTENT与MATCH_PARENT的时候效果是一样的。当然具体的Dp就当然是具体啦。

所以我们在写自定义View时候不重写onMeasure该方法,就会发现WRAP_CONTENT是没有效果的。原因就是这样啦。

其实有看个我的ImageView源码分析ImageView核心源码分析都会发现每个View都会对onMeasure方法作不同程度的处理。下面看看一个简单例子到底Android系统是如何测量宽高的。

例子分析Android测量

  
    

布局文件的代码很简单:一个自定义的ViewGroup和Button

自定义CostomViewGroup中的onMeasure方法

@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  
       //调用ViewGroup类中测量子类的方法  
       measureChildren(widthMeasureSpec, heightMeasureSpec);  
       //调用View类中默认的测量方法  
       super.onMeasure(widthMeasureSpec,heightMeasureSpec);  
  
}  

效果图:


View的测量_第6张图片
Paste_Image.png

measureChildren(widthMeasureSpec, heightMeasureSpec);

View的测量_第7张图片
Paste_Image.png
View的测量_第8张图片
Paste_Image.png

上面的截图是测量子View的,其实也很简单:
将可以显示的子View进行获取measureSpec;而子View的MeasureSpec是由parentMeasureSpec和padding和子View自己的宽高决定的。即下面这句代码:

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);

最后就是child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
即测量子View的真正宽高。这里measure方法就会去调用onMeasure方法。

这里总结一下:

  • 一个ViewGroup的宽高是由里面的子View决定的。当然这里只是个抽象描述。具体应该包含padding、MeasureSpec的Mode、子View的宽高;
  • ChildView即子View的宽高是由 自己的宽高(即在布局文件写的宽高)、padding、父MeasureSpec决定
  • 最后一个其实在上面两个总结的基础之上得到:View的测量是一个迭代进行的,相互制约。

最后再看看getChildMeasureSpec(int spec, int padding, int childDimension)

//传入的参数:parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width
 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;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

最终总结

看完上边的方法我用自己的理解作了下面的总结:
当父是EXACTLY 模式下:

  • 子View有具体的数值就取具体的数值(前提不能大于父的宽高值)
  • 子View是MATCH_PARENT,就给父的宽高值它
  • 子View是WRAP_CONTENT,就给父的最大值它(该值只是一个最大值非子View的最终值)

当父是AT_MOST模式下:

  • 子View有具体的数值就取具体的数值(前提不能大于父的宽高值)
  • 子View是MATCH_PARENT,就给父的宽高值它,这里与上面不同的是子View的Mode为AT_MOST,因为需要后面child.measure(childWidthMeasureSpec, childHeightMeasureSpec);方法中的onMeasure给它具体值和父为AT_MOST确定不了自己的大小。
  • 子View是WRAP_CONTENT,就给父的最大值它(该值只是一个最大值非子View的最终值)情况和上面一样。

最后我想说一下,View的测量其实你只要记住一个View的宽高值是由父View的模式和lp的宽高和padding影响就足够了。因为你看源码(View的源码和ViewGroup的源码)会发现当MatchaParent或WrapContent的时候,源码是没有给出你一个确切的值。WrapContent更是需要你自己定义一个值,如ImageView是WrapContent的时候就是获取图片的宽高值。

参考文章 : ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解

你可能感兴趣的:(View的测量)