最近在练习自定义控件,网上资料太散,自己就记录一下。非常感谢那些分享的人,最后会标注上链接。
自定义控件中三大关键方法:
老大 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 基本说完了。更深层次的使用方法还不清楚哈。
最后再简单再说一下自己学习过程中想弄明白的两个问题
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 里的方法测量子控件,然后再确定自己的宽和高。
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 创建规则,如下图所示。
参考博客:
1.Android自定义View基础之MeasureSpec详解