天天看點

Android繪制時鐘

最近在學View相關的知識,對Canvas和Paint類有了初步了解,看到别人弄了一個會走動的時鐘,自己也打算給弄上一個,算是鞏固一下相關知識。效果圖如下:

Android繪制時鐘

首先來分析一下,要繪制出一個時鐘,要繪制哪幾部分:時鐘的大圓、圓上的刻度線、圓外的數字、圓中心處的圓點以及三個時鐘指針。既然明白了要繪制的東西,下面隻需要想辦法來實作即可。

(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方法了解不夠深入,不能進行靈活應用。

有優化的方案及其他不足之處,還請各位同學多多提意見。

點此源碼下載下傳