天天看点

【Android Canvas系列】使用canvas绘制简单的clock

最近在看HTML5 canvas绘制方面的书籍,感觉和Anroid Canvas方面的知识有比较相通的地方。忽然萌生一个用Android Canvas实现书中例子程序的想法,一来巩固自己Android Canvas方面的知识,二来帮助一些想了解Android Canvas方面知识的新手们。

1. 目标

【Android Canvas系列】使用canvas绘制简单的clock

上图是使用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();
    }
}
           

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 = ;
    fontSize = ;
    int min = Math.min(this.getHeight(), this.getWidth());
    radius = min/ - padding;
    handTruncation = min/;
    hourHandTruncation = min/;
    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();
    paint.setStyle(Paint.Style.STROKE);
    paint.setAntiAlias(true);
    canvas.drawCircle(width/, height/, radius, paint);
}
           

drawCircle就是绘制一个圆圈,这里使用的paint设置style为stroke,表示仅绘制轮廓,不对其内部进行填充。下面的drawCenter函数则要将其设置为fill,因为要绘制一个实心的圆,所以要对其内部填充。

void drawCenter(Canvas canvas) {
    paint.setStyle(Paint.Style.FILL);
    canvas.drawCircle(width/, height/, , 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 /  * (number - );
        x = (int) (width /  + Math.cos(angle) * numeralRadius - fontWidth / );
        y = (int) (height /  + Math.sin(angle) * numeralRadius + fontSize / );
        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 /  - Math.PI / ;
     int handRadius = isHour ? radius - handTruncation - hourHandTruncation : radius - handTruncation;

     canvas.drawLine(width / , height / ,
                (float) (width /  + Math.cos(angle) * handRadius),
                (float) (height /  + Math.sin(angle) * handRadius),
                paint);
 }

 void drawHands(Canvas canvas) {
      Calendar calendar = Calendar.getInstance();
      float hour = calendar.get(Calendar.HOUR_OF_DAY);
      hour = hour >  ? hour -  : hour;

      drawHand(canvas, (hour + calendar.get(Calendar.MINUTE) / f) * f, true);
      drawHand(canvas, calendar.get(Calendar.MINUTE), false);
      drawHand(canvas, calendar.get(Calendar.SECOND), false);
}
           

绘制钟表表针的两个函数。需要留意的是drawLine函数,绘制从一个点至另一个点的直线。drawHands函数里,就是获取当前的时间,然后绘制针表,具体起始点和终点x,y的计算,在草稿纸上写写划划,很容易理解。

4.运行结果

【Android Canvas系列】使用canvas绘制简单的clock

5.代码地址

GitHub代码地址