最近在看HTML5 canvas绘制方面的书籍,感觉和Anroid Canvas方面的知识有比较相通的地方。忽然萌生一个用Android Canvas实现书中例子程序的想法,一来巩固自己Android Canvas方面的知识,二来帮助一些想了解Android Canvas方面知识的新手们。
1. 目标
上图是使用HTML5 Canvas绘制的时钟,本次的目标就是使用Android Canvas实现一个一模一样的时钟。
2.API 列表
- drawCircle //用于绘制钟表外框和中心的小圆点
- drawText //用于绘制钟表外框上的文字
- drawLine //用于绘制钟表的表针
3.代码分析
/**
* Created by sogoe on 2015/3/14.
*/
public class ClockView extends View {
···
@override
protected void onDraw(Canvas canvas) {
if(!isInited)
initClock();
drawCircle(canvas);
drawCenter(canvas);
drawNumeral(canvas);
drawHands(canvas);
this.postInvalidateDelayed(1000);
}
}
layout在计算完布局内每个view的长宽后,会调用view的draw函数。ondraw函数则会在draw函数中被调用,用于绘制内容。那么,关于钟表所有的绘制工作将会在ondraw函数内实现。initClock会执行相关参数的初始化工作,drawCircle是绘制钟表外圈园的函数,drawCenter是绘制钟表中心点的函数,drawNumeral是绘制钟表数字的函数,drawHands是绘制钟表表针的函数。注意,最后的postInvalidateDelayed函数,其内部实现也是使用handler.sendMessageDelayed,然后再handleMessage里执行invalidate函数。invalidate函数会请求重置整个view。
void initClock() {
height = this.getHeight();
width = this.getWidth();
padding = 35;
fontSize = 10;
int min = Math.min(this.getHeight(), this.getWidth());
radius = min/2 - padding;
handTruncation = min/25;
hourHandTruncation = min/10;
numeralRadius = radius + numeralSpacing;
paint = new Paint();
isInited = true;
}
initClock函数就是对钟表的参数进行初始化。或许你会想,初始化为什么不放到构造函数里。仔细看,在代码第一二行里使用了getHeight和getWidth函数,如果放到构造函数里,获取到的长宽为0。因为,在view初始化的时候,系统也并不知道这个view的具体长宽,其父layout会不断调用view的onMeasure函数来计算这个view具体的长宽,所以,执行在ondraw之前,view的长宽才最终被确定下来。因此,在ondraw里执行长宽的初始化最保险。
void drawCircle(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.black));
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
canvas.drawCircle(width/2, height/2, radius, paint);
}
drawCircle就是绘制一个圆圈,这里使用的paint设置style为stroke,表示仅绘制轮廓,不对其内部进行填充。下面的drawCenter函数则要将其设置为fill,因为要绘制一个实心的圆,所以要对其内部填充。
void drawCenter(Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(width/2, height/2, 5, paint);
}
将paint的style设置为fill,绘制实心圆。其他的设置沿用上面函数设置。
void drawNumeral(Canvas canvas) {
paint.setTextSize(fontSize);
float fontWidth;
double angle;
int x, y;
for (int number : numbers) {
fontWidth = paint.measureText(String.valueOf(number));
angle = Math.PI / 6 * (number - 3);
x = (int) (width / 2 + Math.cos(angle) * numeralRadius - fontWidth / 2);
y = (int) (height / 2 + Math.sin(angle) * numeralRadius + fontSize / 3);
canvas.drawText(String.valueOf(number), x, y, paint);
}
}
首先设置字体的大小。留意paint.measureText函数,它返回按照paint当前的配置计算文本在屏幕上所占的长度,在canvas的文本绘制中相当有用。然后就是依次绘制钟表上的数字。x,y坐标点的计算可以在草稿纸上画一下,比较容易理解。
void drawHand(Canvas canvas, double loc, boolean isHour) {
double angle = Math.PI * loc / 30 - Math.PI / 2;
int handRadius = isHour ? radius - handTruncation - hourHandTruncation : radius - handTruncation;
canvas.drawLine(width / 2, height / 2,
(float) (width / 2 + Math.cos(angle) * handRadius),
(float) (height / 2 + Math.sin(angle) * handRadius),
paint);
}
void drawHands(Canvas canvas) {
Calendar calendar = Calendar.getInstance();
float hour = calendar.get(Calendar.HOUR_OF_DAY);
hour = hour > 12 ? hour - 12 : hour;
drawHand(canvas, (hour + calendar.get(Calendar.MINUTE) / 60.f) * 5.f, true);
drawHand(canvas, calendar.get(Calendar.MINUTE), false);
drawHand(canvas, calendar.get(Calendar.SECOND), false);
}
绘制钟表表针的两个函数。需要留意的是drawLine函数,绘制从一个点至另一个点的直线。drawHands函数里,就是获取当前的时间,然后绘制针表,具体起始点和终点x,y的计算,在草稿纸上写写划划,很容易理解。