Android canvas繪圖基礎之運動的時鐘
一、canvas繪製圖形
canvas可以繪製很多幾何圖形、文字等很多東西:
1. drawText
2. drawPoint
3. drawLine
4. drawRect
5. drawCircle
6. drawOval
7. drawArc
8. drawPath
9. drawBitmap
關於繪圖基礎講解這篇部落格講的非常細緻,推薦大家可以去看一下, Android中Canvas繪圖基礎詳解(附原始碼下載),我這裡就不重複講述了。
二、canvas的基礎方法
Canvas作為繪製圖像的直接物件,提供了幾個非常有用的方法:
1. Canvas.save()
2. Canvas.restore()
3. Canvas.translate()
4. Canvas.rotate()
canvas.save()這個方法可以理解為儲存畫布,將之前所有已經繪製的影象儲存下來,讓後續的操作在新的畫布上進行操作,旋轉、移動不影響原來的操作,canvas.restore()就是將sava()之後繪製的所有影象與save()之前繪製的影象進行合併。後面兩個方法就是將畫布平移和旋轉了,理解成將座標系旋轉和平移更加恰當,初看沒有什麼用,其實在我們繪圖當中還是有很大作用的,比如畫時鐘,畫儀表盤,通過旋轉等很輕鬆就可以畫出刻度線,避免複雜的三角函式,我們通過一個畫一個簡單的時鐘小例子來看一下canvas的運用:
我們先設定一些需要用的的變數:
private Paint mPaint;
private Paint mTextPaint;
private Paint mHourPaint;
private Paint mMinutePaint;
private Paint mSecondPaint;
private Paint mPointPaint;
private int mTotalWidth;
private int mTotalHeight;
//秒針長度
private float secondPointerLength;
//分針長度
private float minutePointerLength;
//時針長度
private float hourPointerLength;
//指標反向超過圓點的長度
private static final float POINT_BACK_LENGTH = 40f;
//長刻度線
private static final float LONG_DEGREE_LENGTH = 40f;
//短刻度線
private static final float SHORT_DEGREE_LENGTH = 20f;
//圓的半徑
private float radius;
我們首先繪製一個圓形:
//先畫一個位於螢幕中央、半徑為螢幕寬度一半的圓
radius = mTotalWidth / 2 - mPaint.getStrokeWidth() / 2;
canvas.drawCircle(mTotalWidth / 2, mTotalHeight / 2, radius, mPaint);
然後繪製刻度線:
//接著畫刻度線,整點的刻度線長一點和粗一點
for (int i = 0; i < 60; i++) {
if(i % 5 == 0){
mPaint.setStrokeWidth(5);
canvas.drawLine(radius, mTotalHeight / 2 - radius, radius, mTotalHeight / 2 - radius + LONG_DEGREE_LENGTH, mPaint);
}else{
mPaint.setStrokeWidth(3);
canvas.drawLine(radius, mTotalHeight / 2 - radius, radius, mTotalHeight / 2 - radius + SHORT_DEGREE_LENGTH, mPaint);
}
//設定旋轉角度,以圓心為原點旋轉
canvas.rotate(6, mTotalWidth / 2, mTotalHeight / 2);
}
畫一根線段還是非常簡單的,確定兩個端點的座標就可以了,如果要把時鐘上所有的刻度線畫出來並確定所有的座標點,後面的座標並不是簡單的通過座標加減就能實現的,必須要通過角度計算三角函式才能實現,比如我們計算刻度為1的刻度線座標:
//圓的半徑
float r = getWidth() / 2 - paint.getStrokeWidth() / 2;
//繪製一個圓
canvas.drawCircle(getWidth() / 2, getHeight() / 2, r, paint);
paint.setColor(Color.BLUE);
canvas.save();
//座標系移動到圓心
canvas.translate(getWidth() / 2, getHeight() / 2);
//繪製1的刻度點
canvas.drawLine((float)((r-20) * Math.sin(30*Math.PI/180)), -(float)((r-20) * Math.cos(30*Math.PI/180)), (float)(r * Math.sin(30*Math.PI/180)), -(float)(r * Math.cos(30*Math.PI/180)), paint);
canvas.restore();
畫出來大概就是這個樣子:
這樣就是要算好角度,比起旋轉要麻煩得多,有時候還是要善於利用一些方法,事半功倍。這裡大家要注意一點的是,我們canvas的座標系有且只有一個,座標原點在View的左上角,從座標原點向右為x軸的正半軸,從座標原點向下為y軸的正半軸。座標系移動到圓點,那麼還是以原點向右為x軸的正半軸,原點向下為y軸的正半軸,但是角度是與Y軸的負半軸的夾角,比如說刻度1的30度就是與Y軸的負半軸的夾角,計算座標的時候算出來的Y軸就是負的,X軸還是正的,這裡要搞清楚這一點,不然這個座標你是計算不準確的,不理解的可以畫一個圖看一下。
下面我們就畫數字,數字不通過旋轉來畫,當然旋轉也是可以畫出來的,這樣主要是畫的好看一點,我們通過過計算座標來畫,先寫一個方法計算座標:
//計算線段的起始座標
private float[] calculatePoint(float angle,float length){
float[] points = new float[4];
if(angle <= 90f){
points[0] = -(float) Math.sin(angle*Math.PI/180) * POINT_BACK_LENGTH;
points[1] = (float) Math.cos(angle*Math.PI/180) * POINT_BACK_LENGTH;
points[2] = (float) Math.sin(angle*Math.PI/180) * length;
points[3] = -(float) Math.cos(angle*Math.PI/180) * length;
}else if(angle <= 180f){
points[0] = -(float) Math.cos((angle-90)*Math.PI/180) * POINT_BACK_LENGTH;
points[1] = -(float) Math.sin((angle-90)*Math.PI/180) * POINT_BACK_LENGTH;
points[2] = (float) Math.cos((angle-90)*Math.PI/180) * length;
points[3] = (float) Math.sin((angle-90)*Math.PI/180) * length;
}else if(angle <= 270f){
points[0] = (float) Math.sin((angle-180)*Math.PI/180) * POINT_BACK_LENGTH;
points[1] = -(float) Math.cos((angle-180)*Math.PI/180) * POINT_BACK_LENGTH;
points[2] = -(float) Math.sin((angle-180)*Math.PI/180) * length;
points[3] = (float) Math.cos((angle-180)*Math.PI/180) * length;
}else if(angle <= 360f){
points[0] = (float) Math.cos((angle-270)*Math.PI/180) * POINT_BACK_LENGTH;
points[1] = (float) Math.sin((angle-270)*Math.PI/180) * POINT_BACK_LENGTH;
points[2] = -(float) Math.cos((angle-270)*Math.PI/180) * length;
points[3] = -(float) Math.sin((angle-270)*Math.PI/180) * length;
}
return points;
}
//儲存當前畫布
canvas.save();
//設定文字大小,繪製刻度數字
mTextPaint.setTextSize(30);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setFakeBoldText(true);
for (int i = 0; i < 12; i++) {
String degree = (i + 1) + "";
float[] temp = calculatePoint((i + 1) * 30, radius - mTextPaint.getTextSize() / 2 - LONG_DEGREE_LENGTH - 10);
canvas.drawText(degree, temp[2],temp[3] + mTextPaint.getTextSize() / 2, mTextPaint);
}
下面我們就繪製時針分針
Calendar calendar = Calendar.getInstance();
float[] hourPoint = calculatePoint(calendar.get(Calendar.HOUR_OF_DAY) % 12 / 12f * 360, hourPointerLength);
float[] minutePoint = calculatePoint(calendar.get(Calendar.MINUTE) / 60f * 360, minutePointerLength);
float[] secondPoint = calculatePoint(calendar.get(Calendar.SECOND) / 60f * 360, secondPointerLength);
//繪製時針
canvas.drawLine(hourPoint[0], hourPoint[1], hourPoint[2],hourPoint[3], mHourPaint);
//繪製分針
canvas.drawLine(minutePoint[0], minutePoint[1], minutePoint[2], minutePoint[3], mMinutePaint);
//繪製秒針
canvas.drawLine(secondPoint[0], secondPoint[1], secondPoint[2], secondPoint[3], mSecondPaint);
這樣畫時針分針我們就可以通過獲取系統時間根據角度計算出線段的起始座標,就可以讓時鐘動起來了,當然還是可以通過旋轉來實現的:
Calendar calendar = Calendar.getInstance();
float[] hourPoint = calculatePoint(0, hourPointerLength);
float[] minutePoint = calculatePoint(0, minutePointerLength);
float[] secondPoint = calculatePoint(0, secondPointerLength);
//繪製時針
canvas.save();
canvas.rotate(calendar.get(Calendar.HOUR_OF_DAY) % 12 / 12f * 360, 0, 0);
canvas.drawLine(hourPoint[0], hourPoint[1], hourPoint[2],hourPoint[3], mHourPaint);
canvas.restore();
//繪製分針
canvas.save();
canvas.rotate(calendar.get(Calendar.MINUTE) / 60f * 360, 0, 0);
canvas.drawLine(minutePoint[0], minutePoint[1], minutePoint[2], minutePoint[3], mMinutePaint);
canvas.restore();
//繪製秒針
canvas.save();
canvas.rotate(calendar.get(Calendar.SECOND) / 60f * 360, 0, 0);
canvas.drawLine(secondPoint[0], secondPoint[1], secondPoint[2], secondPoint[3], mSecondPaint);
canvas.restore();
我們在畫一個圓心,就OK了
canvas.drawCircle(0, 0, 2, mPointPaint);
下面通過一個執行緒來不斷使他重繪:
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
invalidate();
handler.sendEmptyMessageDelayed(0, 1000);
break;
default:
break;
}
};
};
然後在初始化的時候傳送一個訊息給handler讓指標運動就好了
//啟動執行緒讓指標運動
handler.sendEmptyMessage(0);
現在這個圓大功告成
canvas也是2D繪圖的基礎,不過掌握好對我們自定義view有著指導性的作用。
其實canvas可以繪製各種圖示,簡單靈活,可以說是非常強大,各位可以下去多多練習,我在github上看到了一篇關於canvas繪圖的圖示庫十分強大,有興趣的可以去看看,https://github.com/xcltapestry/XCL-Charts,非常不錯的一個庫。