前言
在Android开发过程中,我们可能会经常去自定义View,在自定义View之前,我们必须了解清楚系统是如何绘制出这些View的。
一、测量
首先,我们的系统会先测量这个View的大小,以便知道该画多大的一个View。这个过程在onMeasure()方法中进行。
首先我们需要了解这样一个强大的类——MeasureSpec,它来帮助我们测量View。
MeasureSpec是一个32位的int值,高2位为测量的模式,低30位为测量的大小。
1.1三种测量模式
EXACTLY
精确值模式,为我们控件的layout_width或者layout_height属性指定具体的数值,或者指定位match_parent时,用此模式。AT_MOST
最大值模式,为我们控件的layout_width或者layout_height属性指定为warp_content时,控件大小随着子控件或者内容的变化而变化,只要不超过父控件允许的最大尺寸即可。UNSPECIFIED
不指定其大小测量模式,View想多大就多大,通常在自定义View时才使用。
默认情况下,omMeasure()只支持EXACTLY,如果自定义View时不重写onMeasure()的话,就只能使用EXACTLY,如要支持warp_content的话,则必须重写onMeasure()来指定其大小。
1.2具体使用
在onMeasure()方法中,系统最终会调用setMeasuredDimension(int measuredWidth, int measuredHeight)来设置具体的宽高值,所以我们在重写onMeasure()方法时,把测量后的宽高传入setMeasuredDimension()中就可以了。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultWidthSize(widthMeasureSpec);
int height = getDefaultHeightSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
我们调用getDefaultWidthSize()、getDefaultHeightSize()对宽高重新定义
private int getDefaultWidthSize(int measureSpec) {
int result = 0;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
//准确的值 parent dx
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
//最少50+padding
result = 50 + getPaddingLeft() + getPaddingRight();
//取指定的大小和specSize(默认为父布局这么大)的最小一个作为测量值
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
现在,我们的测量工作就做好了。
二、绘制
对于绘制工作,我们需要重写onDraw()方法,在Canvas上绘制所需要的图像。对于不同的View,绘制的难易程度也不一样,我们这里就以一个圆来介绍。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(width, height)/2;
backgroundPaint.setColor(getBackgroundViewColor());
canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2,radius, backgroundPaint);
}
三、布局
我们都知道,ViewGroup会去管理View,当我们的ViewGroup大小为wrap_content时,ViewGroup就需要对子View进行遍历,以便获得子View的大小来设置自己的大小。
对子View的测量就是我们前面讲到的测量方法。测量完毕后,要将子View放到合适的位置,这个过程就是子View的Layout过程,这时候就要遍历调用子View的Layout方法,并知道其具体显示的位置。
对于自定义的ViewGroup,我们需要重写其onLayout()方法来控制子View显示位置的逻辑。具体的操作我们在这里并不细讲,同学们可以参考其他的博客。
通讯录首字显示View
这是我写的一个自定义View,使用于通讯录等地方,能显示列表的首字或者前3字,可以设置背景、文字颜色等。
Github地址:https://github.com/Charon1997/TextCircleImageView
更多精彩内容尽在Charon的小屋