一般来说,对于自定义View,我们有一套自己的开发策略,大致会经过如下几个步骤:
1. 整理自定义View需要定义的属性,这个主要是考虑到View的复用,将它一些可以更改设定的属性提取出来,这些属性主要指非layout_开头的属性,将这些属性以View的类型定义在attrs.xml文件中,如下代码所示:
<declare-styleable name="CustomView">
<attr name="foreColor" format="color" />
<attr name="backColor" format="color" />
<attr name="strokeWidth" format="integer|dimension" />
<attr name="circleSpeen" format="integer" />
<attr name="circleRadius" format="integer|dimension" />
</declare-styleable>
这个过程不是必须的,因为我们可以在自定义View的类中增加相关属性成员变量,然后提供设置这些属性的方法,运行时动态设定,也是OK的,如下代码所示:
// 定义属性成员变量
private int mForeColor;
private int mBackColor;
// 提供对外设置属性的方法
public void setBackColor(int backColor) {
this.mBackColor = backColor;
}
public void setForeColor(int foreColor) {
this.mForeColor = foreColor;
}
...
// 外界根据自定义View对象来设置其属性
mFirstCircle.setBackColor(Color.MAGENTA);
mFirstCircle.setForeColor(Color.GREEN);
2. 如果有在attrs.xml中设置属性,那么在自定义View的构造函数中,需要将这些自定义的属性读进来,方便后续画图的时候用上,读取过程如下所示:
public CustomView(Context context) {
super(context, null);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
// 注意此处不能调super方法,必须用我们自己写的构造方法,不然就白写了
this(context, attrs, 0);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray typedAttrbites = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
mForeColor = typedAttrbites.getColor(R.styleable.CustomView_foreColor, Color.GRAY);
mBackColor = typedAttrbites.getColor(R.styleable.CustomView_backColor, Color.BLUE);
mCircleSpeed = typedAttrbites.getInt(R.styleable.CustomView_circleSpeen, 20);
mCircleRadius = typedAttrbites.getInt(R.styleable.CustomView_circleRadius, 30);
mCircleStrokeWidth = typedAttrbites.getInt(R.styleable.CustomView_strokeWidth, 30);
mPaint = new Paint();
mCircleProcess = 0;
mKeepGoing = true;
new Thread(mGoingRunnable).start();
}
3. 重写onMeasure方法,来确定自定义View的大小,大家都知道,尺寸计算模式有三种:
MeasureSpec.EXACTLY:顾名思义,精确尺寸的意思,适用于给自定义View在layout文件中设置精确的宽高、或者使用match_parent的情况。
MeasureSpec.AT_MOST:自定义View有多大就设置多大的尺寸,适用于宽高设置为wrap_content的情况。
MeasureSpec.UNSPECIFIED:SDK中的解释:The parent has not imposed any constraint on the child. It can be whatever size it wants. 我们写布局时很少出现宽高完全未知的情况,没用过,不考虑。
有了这个基本的概念后,我们对于确定自定义View的大小就比较方便啦,代码如下所示:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int desireWidth;
// 如果是精确尺寸,那设置多大就是多大
if (widthMode == MeasureSpec.EXACTLY) {
desireWidth = widthSize;
} else {
// 如果是wrap_content尺寸,那么根据自定义View的情况来计算高度
// 比如此处自定义View是一个圆环,那么它的宽度就是:半径*2+圆环宽度
desireWidth = mCircleRadius * 2 + mCircleStrokeWidth;
}
int desireHeight;
if (heightMode == MeasureSpec.EXACTLY) {
desireHeight = heightSize;
} else {
desireHeight = mCircleRadius * 2 + mCircleStrokeWidth;
}
setMeasuredDimension(desireWidth, desireHeight);
}
4. 重写onDraw,尺寸确定后,就可以根据需求画图了,onDraw方法提供一个参数Canvas,即画布的意思,它提供很多画各种不同形状的API,比如画圆drawCircle、画矩形drawRect等等,加上画笔Paint(一般定义为成员变量,在构造方法中初始化),就可以想怎么画就怎么画了,如下代码画的是一个待进度的圆环。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int circleX = getWidth() / 2;
int circleY = getHeight() / 2;
mPaint.setColor(mBackColor);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mCircleStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(circleX, circleY, mCircleRadius, mPaint);
mPaint.setColor(mForeColor);
RectF rectF = new RectF(circleX - mCircleRadius, circleY - mCircleRadius,
circleX + mCircleRadius, circleY + mCircleRadius);
canvas.drawArc(rectF, -90, mCircleProcess, false, mPaint);
}
至此,简单的自定义View绘制基本就完成了。
过程不复杂,主要是开发之前要想好需要定义哪些属性,属性确定后,想好宽高怎么计算,然后设定好绘制过程,查好相关的API就可以了,一步一步来,先确定好思路再下手开发~