最近在學View相關的知識,對Canvas和Paint類有了初步了解,看到别人弄了一個會走動的時鐘,自己也打算給弄上一個,算是鞏固一下相關知識。效果圖如下:
首先來分析一下,要繪制出一個時鐘,要繪制哪幾部分:時鐘的大圓、圓上的刻度線、圓外的數字、圓中心處的圓點以及三個時鐘指針。既然明白了要繪制的東西,下面隻需要想辦法來實作即可。
(1)繪制時鐘的大圓
這個最簡單,隻要确定好圓中心坐标點和半徑即可
(2)繪制圓上的刻度線
一個時鐘總共60個刻度線,通過canvas的rotate()方法,進行不斷的旋轉,每次旋轉的角度為360/60;
(3)繪制圓外的數字
和上面一樣,進行旋轉繪制即可;
(4)繪制圓中心處的圓點
和(1)一樣,隻是注意更改一下畫筆的設定;
(5)繪制三個時鐘指針
根據目前的時分秒,計算出每個指針的旋轉角度,在直接繪制線段即可;
注意到,時鐘是每秒走一次,也即我們每秒就要重新整理一次界面,重複一次上面的繪制工作。是以,一初始化時鐘View的時刻,我們通過Handler弄一個定時器,每秒進行重新整理操作。
ClockView源碼如下:
package com.scu.lly.drawclock.view;
import java.util.Calendar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
public class ClockView extends View {
private Paint mPaint;
/**
* 描邊線的粗細
*/
private int strokeWidth = 2;
/**
* 時鐘是否在走(即是否第一次onDraw)
*/
private boolean isRunning;
private Handler mHandler;
private Runnable clockRunnable;
/**
* 時鐘圓的半徑
*/
private int radius = 150;
private String[] clockNumbers = {"12","1","2","3","4","5","6","7","8","9","10","11"};
/**
* 時鐘上需要繪制的數字
*/
private String num;
/**
* 用于測量文本的寬、高度(這裡主要是來擷取高度)
*/
private Rect textBounds = new Rect();
private Calendar cal;
private int hour,min,second;
private float hourAngle,minAngle,secAngle;
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ClockView(Context context) {
super(context);
init();
}
private void init() {
mPaint = new Paint();
mHandler = new Handler();
// cal = Calendar.getInstance();
clockRunnable = new Runnable() {//裡面做的事情就是每隔一秒,重新整理一次界面
@Override
public void run() {
//線程中重新整理界面
postInvalidate();
mHandler.postDelayed(this, 1000);
}
};
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(!isRunning){
runClock();
}else{
initPaint();
//繪制圓形部分
drawClockCircle(canvas);
//繪制刻度線
drawClockScale(canvas);
//繪制數字
drawClockNumber(canvas);
//繪制中心原點
drawClockDot(canvas);
//繪制三個指針
drawClockPoint(canvas);
}
}
/**
* 繪制三個指針
* @param canvas
*/
private void drawClockPoint(Canvas canvas) {
cal = Calendar.getInstance();
hour = cal.get(Calendar.HOUR);//Calendar.HOUR擷取的是12小時制,Calendar.HOUR_OF_DAY擷取的是24小時制
min = cal.get(Calendar.MINUTE);
second = cal.get(Calendar.SECOND);
//計算時分秒指針各自需要偏移的角度
hourAngle = (float)hour / 12 * 360 + (float)min / 60 * (360 / 12);//360/12是指每個數字之間的角度
minAngle = (float)min / 60 * 360;
secAngle = (float)second / 60 * 360;
//下面将時、分、秒指針按照各自的偏移角度進行旋轉,每次旋轉前要先儲存canvas的原始狀态
canvas.save();
canvas.rotate(hourAngle,getWidth() / 2, getHeight() / 2);
canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - 65, mPaint);//時針長度設定為65
canvas.restore();
canvas.save();
canvas.rotate(minAngle,getWidth() / 2, getHeight() / 2);
canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - 90 , mPaint);//分針長度設定為90
canvas.restore();
canvas.save();
canvas.rotate(secAngle,getWidth() / 2, getHeight() / 2);
canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - 110 , mPaint);//秒針長度設定為110
canvas.restore();
}
/**
* 繪制中心原點
*/
private void drawClockDot(Canvas canvas) {
mPaint.reset();
// mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 6, mPaint);
initPaint();
}
/**
* 繪制數字(從正上方12點處開始繪制)
* @param canvas
*/
private void drawClockNumber(Canvas canvas) {
//先儲存一下目前畫布的狀态,因為後面畫布會進行旋轉操作,而在繪制完刻度後,需要恢複畫布狀态
canvas.save();
// mPaint.setStrokeWidth(2);
mPaint.setTextSize(25);
//計算12點處 數字 的坐标
int preX = getWidth() / 2;
int preY = getHeight() / 2 - radius - strokeWidth - 10;//10為圓與數字文本之間的間距
//x,y才是文本真正的準确坐标,需要減去文本的自身寬、高因素
int x,y;
//計算畫布每次需要旋轉的角度
int degree = 360 / clockNumbers.length;
for(int i = 0; i < clockNumbers.length; i++){
num = clockNumbers[i];
mPaint.getTextBounds(num, 0, num.length(), textBounds);
x = (int) (preX - mPaint.measureText(num) / 2);
y = preY - textBounds.height();//從文本的中心點處開始繪制
canvas.drawText(num, x, y, mPaint);
canvas.rotate(degree, getWidth() / 2, getHeight() / 2);//以圓中心進行旋轉
}
//繪制完後,記得把畫布狀态複原
canvas.restore();
}
/**
* 繪制刻度線(總共60條)
* 從正上方,即12點處開始繪制一條直線,後面的隻是旋轉一下畫布角度即可
* @param canvas
*/
private void drawClockScale(Canvas canvas) {
//先儲存一下目前畫布的狀态,因為後面畫布會進行旋轉操作,而在繪制完刻度後,需要恢複畫布狀态
canvas.save();
//計算12點處刻度的開始坐标
int startX = getWidth() / 2;
int startY = getHeight() / 2 - radius;//y坐标即園中心點的y坐标-半徑
//計算12點處的結束坐标
int stopX = startX;
int stopY1 = startY + 30;//整點處的線長度為30
int stopY2 = startY + 15;//非整點處的線長度為15
//計算畫布每次旋轉的角度
float degree = 360 / 60;
for(int i = 0; i < 60; i++){
if(i % 5 == 0)
canvas.drawLine(startX, startY, stopX, stopY1, mPaint);//繪制整點長的刻度
else
canvas.drawLine(startX, startY, stopX, stopY2, mPaint);//繪制非整點處短的刻度
canvas.rotate(degree, getWidth() / 2, getHeight() / 2);//以圓中心進行旋轉
}
//繪制完後,記得把畫布狀态複原
canvas.restore();
}
/**
* 繪制時鐘的圓形部分
* @param canvas
*/
private void drawClockCircle(Canvas canvas) {
//獲得圓的圓點坐标
int x = getWidth() / 2;
int y = getHeight() / 2;
canvas.drawCircle(x, y, radius, mPaint);
}
private void initPaint() {
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);//設定描邊
mPaint.setStrokeWidth(strokeWidth);//設定描邊線的粗細
mPaint.setAntiAlias(true);//設定抗鋸齒,使圓形更加圓滑
}
private void runClock() {
isRunning = true;
mHandler.postDelayed(clockRunnable, 1000);
}
}
注:上面一些計算隻要把草圖畫好,計算坐标就好。
缺陷:
從效果圖中可以看到一個明顯的不足就是,繪制的數字按一個方向旋轉後,效果不滿意。我嘗試了分段繪制,比如将12個數字分成四組{12,,1,2},{3,4,5},{6,7,8},{9,10,11},還是不盡人意。我最主要的問題就是卡在canvas的rotate()旋轉方法的了解上,查了很多資料,基本上認同的是旋轉的坐标,看時鐘的效果也的确如此,但是目前主要是對rotate方法了解不夠深入,不能進行靈活應用。
有優化的方案及其他不足之處,還請各位同學多多提意見。
點此源碼下載下傳