Android开发Android开发经验谈首页投稿(暂停使用,暂停投稿)

View的测量

2017-10-26  本文已影响41人  721d739b6619

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

onMeasure()

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方法传入的参数。看看干什么的:

getDefaultSize

这里简单解释一下:

Paste_Image.png Paste_Image.png 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测量

<com.text.CostomViewGroup xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="#bbbaaa"  
    >  
    <Button  
        android:text="@string/hello_world"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:background="#aaabbb"  
        android:id="@+id/textView1" />  
</com.text.CostomViewGroup >  

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

自定义CostomViewGroup中的onMeasure方法

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

效果图:


Paste_Image.png

measureChildren(widthMeasureSpec, heightMeasureSpec);

Paste_Image.png 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方法。

这里总结一下:

最后再看看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 模式下:

当父是AT_MOST模式下:

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

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

上一篇下一篇

猜你喜欢

热点阅读