自定义view-onMeasure的理解

来源:互联网 发布:oracle数据库游标分页 编辑:程序博客网 时间:2024/04/29 07:18

参考:Android自定义View(三、深入解析控件测量onMeasure)

onMeasure方法是由父控件调用的,所有父控件都是ViewGroup的子类,
ViewGroup是一个抽象类,它里面有一个抽象方法onLayout,这个方法的作用就是摆放它所有的子控件(安排位置),
因为是抽象类,不能直接new对象,所以我们在布局文件中可以使用View但是不能直接使用 ViewGroup。

在给子控件确定位置之前,必须要获取到子控件的大小(只有确定了子控件的大小才能正确的确定上下左右四个点的坐标),
而ViewGroup并没有重写View的onMeasure方法,也就是说抽象类ViewGroup没有为子控件测量大小的能力,它只能测量自己的大小。
但是既然ViewGroup是一个能容纳子控件的容器,系统当然也考虑到测量子控件的问题,
所以ViewGroup提供了三个测量子控件相关的方法(measuireChildren\measuireChild\measureChildWithMargins),
只是在ViewGroup中没有调用它们,所以它本身不具备为子控件测量大小的能力。

为什么都有测量子控件的方法了而ViewGroup中不直接重写onMeasure方法,然后在onMeasure中调用呢?
因为不同的容器摆放子控件的方式不同,比如RelativeLayout,LinearLayout这两个ViewGroup的子类,
它们摆放子控件的方式不同,有的是线性摆放,而有的是叠加摆放,这就导致测量子控件的方式会有所差别,
所以ViewGroup就干脆不直接测量子控件,他的子类要测量子控件就根据自己的布局特性重写onMeasure方法去测量。

测量的时候父控件的onMeasure方法会遍历他所有的子控件,挨个调用子控件的measure方法,measure方法会调用onMeasure,
然后会调用setMeasureDimension方法保存测量的大小,一次遍历下来,第一个子控件以及这个子控件中的所有子控件都会完成测量工作;
然后开始测量第二个子控件…;最后父控件所有的子控件都完成测量以后会调用setMeasureDimension方法保存自己的测量大小。
值得注意的是,这个过程不只执行一次,也就是说有可能重复执行,
因为有的时候,一轮测量下来,父控件发现某一个子控件的尺寸不符合要求,就会重新测量一遍。

如果要自定义ViewGroup就必须重写onMeasure方法,在这里测量子控件的尺寸。子控件的尺寸怎么测量呢?ViewGroup中提供了三个关于测量子控件的方法,源码如下:

/** *遍历ViewGroup中所有的子控件,调用measuireChild测量宽高 */  protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {      final int size = mChildrenCount;      final View[] children = mChildren;      for (int i = 0; i < size; ++i) {          final View child = children[i];          if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {              measureChild(child, widthMeasureSpec, heightMeasureSpec);          }      }  } /** * 测量某一个child的宽高 */  protected void measureChild(View child, int parentWidthMeasureSpec,          int parentHeightMeasureSpec) {      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);  }/** * 测量某一个child的宽高,考虑margin值 */  protected void measureChildWithMargins(View child,          int parentWidthMeasureSpec, int widthUsed,          int parentHeightMeasureSpec, int heightUsed) {      final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();      final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,              mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                      + widthUsed, lp.width);      final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,              mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin                      + heightUsed, lp.height);      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  }

这里写图片描述

onMeasure源码:

protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {    setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}/** * 为宽度获取一个建议最小值 */protected int getSuggestedMinimumWidth () {    return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground.getMinimumWidth());}/** * 获取默认的宽高值 */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;}

从源码我们了解到:

如果View的宽高模式为未指定,他的宽高将设置为android:minWidth/Height =”“值与背景宽高值中较大的一个;
如果View的宽高 模式为 EXACTLY (具体的size ),最终宽高就是这个size值;
如果View的宽高模式为EXACTLY (填充父控件 ),最终宽高将为填充父控件;
如果View的宽高模式为AT_MOST (包裹内容),最终宽高也是填充父控件。

如果我们的自定义控件在布局文件中,只需要设置指定的具体宽高,或者MATCH_PARENT 的情况,我们可以不用重写onMeasure方法。
如果自定义控件需要设置包裹内容WRAP_CONTENT ,我们需要重写onMeasure方法,为控件设置需要的尺寸;默认情况下WRAP_CONTENT 的处理也将填充整个父控件。
onMeasure方法最后需要调用setMeasuredDimension方法来保存测量的宽高值。

测量控件大小是父控件发起的
父控件要测量子控件大小,需要重写onMeasure方法,然后调用measureChildren或者measureChildWithMargin方法
on Measure方法的参数是通过getChildMeasureSpec生成的
如果我们自定义控件需要使用wrap_content,我们需要重写onMeasure方法
测量控件的步骤:
父控件onMeasure->measureChildrenmeasureChildWithMargin->getChildMeasureSpec->
子控件的measure->onMeasure->setMeasureDimension->
父控件onMeasure结束调用setMeasureDimension
保存自己的大小

自定义ViewGroup的步骤定为下面几步:
1. 继承ViewGroup,覆盖构造方法
2. 重写onMeasure方法测量子控件和自身宽高
3. 实现onLayout方法摆放子控件

自定义view的方法调用流程:

View被创建onFinishInflate()onMeasure()onMeasure()onSizeChanged(),w:1080,h:1080,oldw:0,oldh0onLayout()onDraw()