Android-自定义控件之onMeasure浅谈

最近在练习自定义控件,网上资料太散,自己就记录一下。非常感谢那些分享的人,最后会标注上链接。

自定义控件中三大关键方法:
老大 onMeasure
老二 onLayout
老三 onDraw

Android 中一切显示控件皆继承至 View,而 onMeasure 则用于测量 View 的大小,只有先确定了大小,才能开始其他绘制的后续工作。

但是不了解就无法写自定义控件了么?或者说必须要去处理 onMeasure 么?
并不是所有情况下都需要去管onMeasure!比如你的控件大小是固定的,500dp。又比如你的控件是填充父控件的。这两种情况并不需要去管onMeasure,系统会处理。但是如果你的控件
想仅仅包裹内容,并且可以随着父控件的大小变化而适应,就必须手动处理 onMeasure,而这种情况恰恰是最常用的。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){}

它有两个参数,刚开始看的我一脸懵逼。而它们其实是对应的源码里的 MeasureSpec 类,该类提供相关的方法去解析。其含义需要把它转换为二进制去理解,int的二进制位数是32位。最高的两位代表 SpecMode(测量模式)。后30位代表 SpecSize(指的是某种测量模式下的规格大小)。
例如 widthMeasureSpec 的解析方式:

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);

此时我们拿到了当前 View 宽度的 SpecMode(widthMode) 和 SpecSize(widthSize)。
SpecMode 一共有三种类型
1.EXACTLY : 表示父视图希望子视图的大小应该是由 specSize 的值来决定的,系统默认会按照这个规则来设置子视图的大小。
2.AT_MOST : 表示子视图最多只能是 specSize 中指定的大小,系统默认会按照这个规则来设置子视图的大小,如果超过 specSize 指定的大小,会导致视图显示不全。
3.UNSPECIFIED: 表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制,可能是系统内部用的。
而 AT_MOST 就是我们上面说的需要处理的情况。
比如我有个控件,设计大小是500dp,通过addview添加到父控件。

MyView mView = new MyView(this);
parent.addView(mView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));

onMeasure 触发!这时我们拿到的 SpecMode 就会是 AT_MOST。
而 SpecSize 会有两种情况:
1.父控件给我们的空间大于等于500,而我们设计的是500,所以我们最终可以设置为500以达到最好效果。
2.父控件给我们的空间小于500,我们就要对显示内容进行缩放了,比如控件中显示的图片!
设置最终大小的方法是如下所示:

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {}

到这里 onMeasure 基本说完了。更深层次的使用方法还不清楚哈。
最后再简单再说一下自己学习过程中想弄明白的两个问题

  1. 父控件视图的 SpecMode 和 SpecSize 是怎么确定的?
    追根溯源,就需要了解根视图的 SpecMode 和 SpecSize。首先 Activity 通过 setContentView 设置根视图后,根视图的 Measure 测量流程 :
    省略 … ViewRootImpl(measureHierarchy) -> ViewRootImpl(getRootMeasureSpec) -> ViewRootImpl(performMeasure) … 省略
    getRootMeasureSpec 方法就会确定 SpecMode 和 SpecSize。
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
       int measureSpec;
       switch (rootDimension) {

       case ViewGroup.LayoutParams.MATCH_PARENT:
           // Window can't resize. Force root view to be windowSize.
           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
           break;
       case ViewGroup.LayoutParams.WRAP_CONTENT:
           // Window can resize. Set max size for root view.
           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
           break;
       default:
           // Window wants to be an exact size. Force root view to be that size.
           measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
           break;
       }
       return measureSpec;
   }

通过源码可知当根视图的宽和高使用 WRAP_CONTENT, SpecMode 是 AT_MOST,SpecSize 是 windowSize。 当视图的宽和高使用 MATCH_PARENT 或者是一个具体的数值,SpecMode 是 EXACTLY。SpecSize 是 windowSize 或 rootDimension(设定的大小dp)
最终根视图的 onMeasure 会被回调。若根视图是一个容器,则开始使用 ViewGroup 里的方法测量子控件,然后再确定自己的宽和高。

  1. 子控件的 SpecMode 和 SpecSize 是怎么确定的?
    通过查看源码 ViewGroup(getChildMeasureSpec) 方法可知,子控件的 SpecMode 是由父容器的 SpecMode 和子控件本身设置的 layoutParams 值共同决定的。
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);
   }

子控件的 SpecMode 创建规则,如下图所示。
Android-自定义控件之onMeasure浅谈_第1张图片
参考博客:
1.Android自定义View基础之MeasureSpec详解

你可能感兴趣的:(Android)