自定义View(三)之View类的onMeasure方法详解

来源:互联网 发布:知乎手机新注册不了 编辑:程序博客网 时间:2024/04/29 12:18

一,概述

在View(一)之初识自定义View中的第五条中已经简单将解过onMeasure方法,我们知道onMeasure方法的作用是测量,测量view的宽高。一般情况下测量的宽高就是实际显示的宽高,但也有特殊情况,特殊情况在ViewGroup的onLayout方法中讲解,目前先认为测量的宽高就等于实际显示的宽高。

下面先讲解onMeasure方法的基本用法,然后再分析onMeasure方法的源码。

二,onMeasure方法的基本讲解

1,onMeasure方法

重写的onMeasure方法如下:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {     super.onMeasure(widthMeasureSpec,heightMeasureSpec); }

onMeasure方法是View的生命周期方法,系统会自动调用。我们知到如果不重写onMeasure方法也可以,因为此时会执行View类的onMeasure方法,View类中的onMeasure方法会解析widthMeasureSpec和heightMeasureSpec参数,然后得到mMeasuredWidth和mMeasuredHeight。onMeasure方法的作用是得到测量宽高,本质就是给View的mMeasuredWidth字段和mMeasuredHeight字段赋值。

系统调用onMeasure方法时传递的参数widthMeasureSpec和heightMeasureSpec与在布局文件中设置的layout_width和layout_height有关。下面重点讲解MeasureSpec类。

2,MeasureSpec类

MeasureSpec类成为测量规则,通过这个类可以把测量模式和测量大小两个int型的数值封装成一个int型的数值。onMeasure方法的参数widthMeasureSpec和heightMeasureSpec就是封装后的数值。当然MeasureSpec类也可以把封装后的数值解析为测量模式和测量大小两个数值。

封装方法如下:

//这个方法需要传递两个参数,参数一是测量大小,参数二是测量模式。int mWidthMeasureSpec = MeasureSpec.makeMeasureSpec(300,widthMode);

解析方法如下:

 //获取测量大小 int widthSize = MeasureSpec.getSize(widthMeasureSpec); //获取测量模式int widthMode = MeasureSpec.getMode(widthMeasureSpec);

MeasureSpec类的作用就是封装数据,没有其他功能。

测量大小很好理解,就是尺寸。那么测量模式是什么呢?

3,测量模式

测量模式有三种,分别如下:
1,MeasureSpec.AT_MOST: 在布局文件中对应的是wrap_content;
2,MeasureSpec.EXACTLY: 在布局文件中对应的是具体的dp值和match_parent;
3,MeasureSpec.UNSPECIFIED: 未定义的,一般只在adapter的测量中,比如ListView。还有在ScrollView中
测量模式有什么作用呢?后面在onMeasure方法中会讲。

4,setMeasuredDimension方法

方法的用法:

//接收两个参数,分别是宽和高setMeasuredDimension(300,300);

使用setMeasuredDimension方法可以直接指定view的宽度和高度。

三,onMeasure方法的用法

我们知道onMeasure方法的作用是测量view的大小。在代码层面的作用是给View的mMeasuredWidth字段和mMeasuredHeight字段赋值。这两个值即是view显示的宽高。

给View设置宽高有三种方法:

1,使用布局文件

自定义View时可以不重写onMeasure方法,它会调用父类的onMeasure方法。onMeasure方法的两个参数与布局文件中的设置有关,所以我们在布局文件中设置layout_width和layout_height两个属性值给View设置宽高。

在布局文件中,宽高指定wrap_content和match_parent都是充满屏幕的,若是固定值则显示固定值。

注:这种方式不用重新onMeasure方法。

2,使用super的onMeasure方法,

我们可以根据MeasureSpec类得到测量规则,然后将新的测量规则传递给super的onMeasure方法。代码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取测量模式        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获取测量模式        int mWidthMeasureSpec = MeasureSpec.makeMeasureSpec(300,widthMode);//得到新的测量规则,宽度为300px        int mHeightMeasureSpec = MeasureSpec.makeMeasureSpec(300,heightMode);//得到新的测量规则,高度为300px        super.onMeasure(mWidthMeasureSpec, mHeightMeasureSpec);    }

注:使用这种方式时,在布局文件中设置的宽高将会没有效果。

3,setMeasuredDimension方法

使用这种方法最简单:

//接收两个参数,分别是宽和高setMeasuredDimension(300,300);

注:使用这种方式时,在布局文件中设置的宽高将会没有效果。

3,三种方法的优缺点

1,如果我们明确知道需要显示的宽度和高度,则不用重写onMeasure方法,直接在布局文件中设置宽度和高度就行。
2,当不在布局文件中使用,或者测量的宽度和高度需要逻辑计算出来,这时就使用setMeasuredDimension方法。
3,当需要指定测量规则,又需要计算时,可以使用第二种方式。

四,onMeasure方法的源码分析

1,onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }

看到这个代码好熟悉的感觉,又看到了setMeasuredDimension方法。但是此时setMeasuredDimension方法的参数是由getDefaultSize方法得到的。下面看getDefaultSize方法的源码。

2,getDefaultSize方法

getDefaultSize方法源码如下:

    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;        case MeasureSpec.AT_MOST:        case MeasureSpec.EXACTLY:            result = specSize;            break;        }        return result;    }

分析:
这个方法中得到了测量模式和测量大小。如果测量模式是UNSPECIFIED,即不确定,即有Listview或ScrollView时是这种模式,这种模式返回值是size。如果是另外两种测量模式则返回测量大小。

下面看这个size值是怎么得到的,在onMeasure方法中使用getSuggestedMinimumWidth方法得到size值,getSuggestedMinimumWidth方法的源码如下:

    protected int getSuggestedMinimumWidth() {        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());    }

首先判断是否有背景,如果没有背景则得到最小尺寸,如果没有 设置最小尺寸则返回0。

3,setMeasuredDimension方法

方法的源码是:

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {        boolean optical = isLayoutModeOptical(this);        if (optical != isLayoutModeOptical(mParent)) {            Insets insets = getOpticalInsets();            int opticalWidth  = insets.left + insets.right;            int opticalHeight = insets.top  + insets.bottom;            measuredWidth  += optical ? opticalWidth  : -opticalWidth;            measuredHeight += optical ? opticalHeight : -opticalHeight;        }        setMeasuredDimensionRaw(measuredWidth, measuredHeight);    }

首先调用了isLayoutModeOptical方法,这是一个很重要的方法,在很多地方有使用。意思是:是否有可见的bound。这个方法的源码是:

    public static boolean isLayoutModeOptical(Object o) {        return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();    }

显然对于View ,isLayoutModeOptical(this)返回值是false,

isLayoutModeOptical方法的源码是:

    boolean isLayoutModeOptical() {        return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;    }

mLayoutMode 的声明是:
private int mLayoutMode = LAYOUT_MODE_UNDEFINED;
一般情况下很少会改变mLayoutMode 的值,所以isLayoutModeOptical(mParent)也返回false。
所以会直接调用setMeasuredDimensionRaw方法。这个方法的源码是:

   private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }

此时给mMeasuredWidth 和mMeasuredHeight 赋值。到此完成了onMeasure方法的任务。

五,总结

1,onMeasure方法的作用

onMeasure方法的作用是得到测量宽高,本质就是给View的mMeasuredWidth字段和mMeasuredHeight字段赋值。然后使用getMeasureWidth方法和getMeasureHeight方法可以得到测量后的宽和高,然后供onLayout方法和onDraw方法使用。

2,onMeasure方法的使用方式

在自定义View时,很多情况下如果该View在布局文件中使用,是不需要重写onMeasure方法的,宽高可以直接在布局文件中设置。

如果不在布局文件中使用,可以直接使用setMeasuredDimension方法。如果需要全屏显示,就获取屏幕的宽度和高度。如果包裹内容显示,就获取内容的宽度和高度,如果指定宽度值和高度值,那就指定高度值和宽度值。

原创粉丝点击