View的测量

作者: 721d739b6619 | 来源:发表于2017-10-26 14:23 被阅读41次

今天讲的是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

这里简单解释一下:

  • MeasureSpec.UNSPECIFIED 为父类不确定模式,大小由子类View确定
  • MeasureSpec.AT_MOST 为最大化模式,即在这个撑满这个最大值
  • MeasureSpec.EXACTLY 为精确模式,即平时具体设定多少Dp,Sp或MATCH_PARENT都是基于这个模式;
    以上三种模式是我基于写代码看源码时的自己理解,具体你可以看下面官方的解释:
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方法。

这里总结一下:

  • 一个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(二) - 测量

    view的测量过程 之所以先讲view的测量过程,是因为ViewGroup测量的时候是先把他的所有子view测量完...

  • Android-View的测量

    Android View的测量 在绘制View之前,要对整个View进行测量,这个过程就在onMeasure()方...

  • Android通过View生成Bitmap

    已测量过的View生成Bitmap 即经过测量、布局、绘制并显示在界面上的View,此类View无需再次进行测量和...

  • Android自定义View笔记

    View的测量: View测量模式有三种:EXACTLY,AT_MOST,UNSPECIFIED。一般测量都在on...

  • 自定义view

    测量,赋值,绘制 测量:父view调用子view的onMeasure()方法,首先看子view是一个view还是v...

  • View测量流程

    View测量流程简介 ViewGroup继承自View,在View的测量方法measure方法中,调用了onMea...

  • android基础-view的测量,布局,绘制

    知识点 view的测量 view的布局 view的绘制 android中的view显示方式主要就是测量出大小→决定...

  • View的测量、布局和绘制过程中父View(当前View)和子V

    View的测量、布局和绘制过程中父View(当前View)和子View的先后顺序 View的测量、布局和绘制过程中...

  • Android view架构

    view测量与绘制 view的测量MeasureSpec:定义:由SpecMode(int)于SpecSize(i...

  • 第八单元

    一、View绘制流程 1、流程 measure:确定View的测量宽高 layout:根据测量的宽高确定View在...

网友评论

    本文标题:View的测量

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