首先這應該是一個老生常談的設計了,但是畢竟身為小白的自己都沒動手做過,不動手怎麼提高自己呢,所以在這梅林沉船閑暇之際,我就把我的設計流程與思路記錄下來。首先來看看效果圖吧: 如上圖就是一個簡單並沒有美化過的時鐘,接下來我就來講講我的設計流程與思路。 一.首先繼承view重寫裡面的onDraw方法。 ...
首先這應該是一個老生常談的設計了,但是畢竟身為小白的自己都沒動手做過,不動手怎麼提高自己呢,所以在這梅林沉船閑暇之際,我就把我的設計流程與思路記錄下來。首先來看看效果圖吧:
如上圖就是一個簡單並沒有美化過的時鐘,接下來我就來講講我的設計流程與思路。
一.首先繼承view重寫裡面的onDraw方法。
我們要搭建好了畫布才能開始在裡面畫畫,而onDraw方法中的canvas當然就是起到畫布的作用。
1 public class MyClockView extends View { 2 3 public MyClockView(Context context) { 4 super(context); 5 init();//初始化的方法 6 } 7 8 public MyClockView(Context context, AttributeSet attrs) { 9 super(context, attrs); 10 init(); 11 } 12 13 public MyClockView(Context context, AttributeSet attrs, int defStyleAttr) { 14 super(context, attrs, defStyleAttr); 15 init(); 16 } 17 18 public void init() { 19 20 } 21 22 @Override 23 protected void onDraw(Canvas canvas) { 24 super.onDraw(canvas); 25 } 26 27 }
二.準備需要用到的工具。
要畫一個時鐘當然首先得要有筆才行,第一步上面我們得到了畫布,現在我們還需要一個paint的畫筆。似乎繪圖用的工具就這麼多了,一張畫布,一支筆,那麼讓我們想想還需要用到些什麼變數,我就先把時鐘結構拆分成了,時、分、秒針,一個圓圈框和時鐘的刻度與數字,但是這些能代表些什麼deep♂dark♂fantastic的呢,再讓我們想想這裡一般繪圖當然要和坐標掛鉤,那就都變成二維坐標吧,時分秒針都是直線,就是畫線,圓框就是畫圓要知道圓心與半徑,刻度也是畫線,數字就是寫字,拆分為了,時分秒針兩端的坐標,圓心與半徑,刻度兩端的坐標,數字繪製開始的坐標。
三.坐標繪製的演算法分析。
我們應該是都知道要想根據坐標繪製就要先知道它的原點在哪,一般預設情況下它的原點定在屏幕左上角,並且y軸下半部為正半軸,上半部為負半軸。如圖:
根據上面圖的坐標系,我們現在要畫一個圓形,裡面再畫刻度,再畫時分秒針,首先我們要確定圓心在哪,我按照習慣以控制項長寬最短的一邊為直徑來定圓心的坐標,我在剛纔繼承view之後重寫onSizeChanged方法(這個方法是當寬高放生變化和第一次會執行)做獲取半徑長:
1 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 2 mWidth = w;//獲得寬度 3 mHeight = h;//獲得高度 4 5 //以最短的一邊為所要繪製圓形的直徑 6 if (mWidth > mHeight) { 7 arcRa = mHeight / 2;//以最短的一邊算出半徑 8 } else { 9 arcRa = mWidth / 2;//以最短的一邊算出半徑 10 } 11 super.onSizeChanged(w, h, oldw, oldh); 12 }
這樣在預設坐標系中,圓心坐標即為(arcRa,arcRa),這裡我用arcRa代表半徑。有了圓心和半徑,那就可以順利的用canvas.drawCircle畫出一個圓。接下來我們要構思畫刻度,刻度說白了就是把一個從圓心來等分,一個圓一圈是360度,也就是說把360度等分了,分多少呢,60秒等於1分鐘,60分鐘等於1小時,那就分60份咯,但是時鐘有長短刻度,小刻度是分成60份了,那代表小時的長刻度怎麼分,轉一圈是12小時,那就分成12份。
具體刻度切分的思路就上面那麼多,那說了這麼多,到底要乾什麼?當然都是為了確定坐標。接下來交給三角函數演算法如下圖:
我們知道了圓心(arcRa,arcRa)和半徑在坐標系中畫圓,x軸與圓圈相交於一點(arcRa,0),連接(arcRa,0)與圓心是一條直線如圖,並且這條直線垂直於x軸。我們要根據360度分出來的度數,來計算刻度坐標點所要在的位置,假設以圓心垂直於x軸的直線來切分,切分出為θ的角度,我們知道半徑arcRa,用三角函數公式可得到如圖所示的坐標點point。
上面就是畫刻度與指針的演算法了,就是簡單的三角函數問題。我用的是三角函數演算法來繪製其實還有另外一種簡單方法,通過旋轉canvas畫布來繪製,網上也能夠查到,我這裡就用我當時順勢想出來的麻煩點蠢的方法來繪製。
四.開始draw啦。
嗨呀,終於可以開始畫了,不過在開始繪製指針之前,先要獲取時間才行:
1 private void getCurrentTime() { 2 long time = System.currentTimeMillis();//獲取時間 3 Calendar mCalendar = Calendar.getInstance(); 4 mCalendar.setTimeInMillis(time); 5 startHour = mCalendar.get(Calendar.HOUR);//獲取小時,12小時制 6 startMinute = mCalendar.get(Calendar.MINUTE);//獲取分鐘 7 startSecond = mCalendar.get(Calendar.SECOND);//獲取秒 8 }
這裡我們獲取到的時間其實就是所占的份數,分鐘與秒都是總共60份,小時為12份。
先畫圓與刻度:
1 //畫圓,通過獲取寬高算出最短一邊作為直徑,坐標原點預設在手機屏幕左上角 2 canvas.drawCircle(arcRa, arcRa, arcRa, paint); 3 4 //圍繞圓形繪製刻度,坐標原點預設在手機屏幕左上角 5 for (int i = 0; i < 60; i++) {///2π圓形分成60份,一秒鐘與一分鐘,所以要繪製60次,這裡是從0到59 6 float x1, y1, x2, y2;//刻度的兩端的坐標即起始於結束的坐標 7 float scale;//每個刻度離圓心的最近端坐標點到圓心的距離 8 Double du = rr * i;//當前所占的角度 9 Double sinx = Math.sin(du);//該角度的sin值 10 Double cosy = Math.cos(du);//該角度的cos值 11 x1 = (float) (arcRa + arcRa * sinx);//以預設坐標系通過三角函數算出刻度離圓心最遠的端點的x軸坐標 12 y1 = (float) (arcRa - arcRa * cosy);//以預設坐標系通過三角函數算出刻度離圓心最遠的端點的y軸坐標 13 if (i % 5 == 0) {//篩選刻度長度 14 scale = 5 * arcRa / 6;//長刻度繪製,刻度離圓心的最近端坐標點到圓心的距離,這裡取半徑的五分之六的長度,可以通過情況來定 15 } else { 16 scale = 9 * arcRa / 10;//短刻度繪製,這裡取半徑的十分之六九的長度,可以通過情況來定 17 } 18 x2 = (float) (arcRa + scale * sinx);//以預設坐標系通過三角函數算出該刻度離圓心最近的端點的x軸坐標 19 y2 = (float) (arcRa - scale * cosy);//以預設坐標系通過三角函數算出該刻度離圓心最近的端點的y軸坐標 20 canvas.drawLine(x1, y1, x2, y2, paint);//通過兩端點繪製刻度 21 }
然後開始繪製時分秒指針:
1 //利用三角函數計算分別計算出,時分秒三針所在的坐標點,坐標原點預設在手機屏幕左上角 2 float sencondScale = 5 * arcRa / 6;//秒針長度 3 float minuteScale = 3 * arcRa / 4;//分針長度 4 float hourScale = arcRa / 2;//時針長度 5 secondStartPoint.x = (float) (arcRa + sencondScale * Math.sin(secondAngle)); 6 secondStartPoint.y = (float) (arcRa - sencondScale * Math.cos(secondAngle)); 7 minuteStartPoint.x = (float) (arcRa + minuteScale * Math.sin(minuteAngle)); 8 minuteStartPoint.y = (float) (arcRa - minuteScale * Math.cos(minuteAngle)); 9 hourStartPoint.x = (float) (arcRa + hourScale * Math.sin(hourAngle)); 10 hourStartPoint.y = (float) (arcRa - hourScale * Math.cos(hourAngle)); 11 //繪製時、分、秒針,坐標原點預設在手機屏幕左上角 12 canvas.drawLine(arcRa, arcRa, secondStartPoint.x, secondStartPoint.y, paint); 13 canvas.drawLine(arcRa, arcRa, minuteStartPoint.x, minuteStartPoint.y, paint); 14 canvas.drawLine(arcRa, arcRa, hourStartPoint.x, hourStartPoint.y, paint);
其實上面都是一個畫線條的過程,也就是知道兩點坐標drawLine的過程。接下來繪製數字,找到需要繪製地點的坐標,我們在長刻度上繪製小時數,一圈12個小時,那就在剛纔上面繪製長刻度裡面加上:
1 //繪製長刻度上的數字1~12 2 String number = itime + "";//當前數字變為String類型 3 itime++;//數字加1 4 if (itime > 12) {//如果大於數字12,重置為1 5 itime = 1; 6 } 7 float numScale = 4 * arcRa / 5;//數字離圓心的距離,這裡取半徑的五分之四的長度,可以通過情況來定 8 float x3 = (float) (arcRa + numScale * sinx);//以預設坐標系通過三角函數算出x軸坐標 9 float y3 = (float) (arcRa - numScale * cosy);//以預設坐標系通過三角函數算出x軸坐標 10 paint.getTextBounds(number, 0, number.length(), textBound);//獲取每個數字被全部包裹的最小的矩形邊框數值 11 12 //繪製數字,通過x3,y3根據文字最小包裹矩形邊框數值進行繪製點調整 13 canvas.drawText(number, x3 - textBound.width() / 2, y3 + textBound.height() / 2, paint);
繪製文字我以為只要drawText出來就行了,沒想到錯位了,我就思考怎麼會這樣,後來發現drawText的參數設定:
/** * text:繪製的文字 * x:繪製原點x坐標 * y:繪製原點y坐標(基線) * paint:用來做畫的畫筆 */ public void drawText(String text, float x, float y, Paint paint)
這裡y是繪製的基線並不是文字中心點,基線這裡我用網上找到的圖來展示一下:
所以還是得我們自己調整下文字的繪製位置,Paint畫筆預設繪製文字(SetTextAlign)是按照左下角紅點開始繪製:
所以我就通過獲得paint.getTextBounds(number,0,number.length(),textBound);獲取每個數字被全部包裹的最小的矩形邊框數值,通過坐標移動將它移動到相應位置。
最後繪製完畢了! (╯‵□′)╯︵┻━┻怎麼放上去不動,不要唬我!那是忘記進行刷新操作,最後在onDraw方法中繪製完畢最後加上這個:
postInvalidateDelayed(1000);//每秒刷新一次
結束放上源碼與神秘鏈接:
GitHub:https://github.com/SteinsGateZero/MyclockViewtest.git
1 public class MyClockView extends View { 2 private Paint paint;//畫筆 3 private int mainColor = Color.parseColor("#000000");//畫筆顏色 4 private float mWidth, mHeight;//視圖寬高 5 private float arcRa = 0;//圓半徑 6 private Double rr = 2 * Math.PI / 60;//2π即360度的圓形分成60份,一秒鐘與一分鐘 7 private Double rr2 = 2 * Math.PI / 12;//2π圓形分成12份,圓形顯示12個小時的刻度 8 private PointF secondStartPoint, minuteStartPoint, hourStartPoint;//秒,分,時的坐標點 9 private int startSecond, startMinute, startHour;//初始化時秒,分,時獲取的系統時間 10 private Rect textBound = new Rect();//字體被全部包裹的最小的矩形邊框 11 12 public MyClockView(Context context) { 13 super(context); 14 init(); 15 } 16 17 public MyClockView(Context context, AttributeSet attrs) { 18 super(context, attrs); 19 init(); 20 } 21 22 public MyClockView(Context context, AttributeSet attrs, int defStyleAttr) { 23 super(context, attrs, defStyleAttr); 24 init(); 25 } 26 27 @Override 28 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 29 mWidth = w;//獲得寬度 30 mHeight = h;//獲得高度 31 32 //以最短的一邊為所要繪製圓形的直徑 33 if (mWidth > mHeight) { 34 arcRa = mHeight / 2;//以最短的一邊算出半徑 35 } else { 36 arcRa = mWidth / 2;//以最短的一邊算出半徑 37 } 38 super.onSizeChanged(w, h, oldw, oldh); 39 } 40 41 public void init() { 42 paint = new Paint();//初始化畫筆 43 paint.setColor(mainColor);//設置顏色 44 // paint.setAntiAlias(true);//抗鋸齒(性能影響) 45 paint.setStyle(Paint.Style.STROKE);//設置畫筆 46 paint.setTextSize(45);//設置字體大小 47 secondStartPoint = new PointF(arcRa, 0);//初始化坐標點 48 hourStartPoint = new PointF(arcRa, 0); 49 minuteStartPoint = new PointF(arcRa, 0); 50 } 51 52 @Override 53 protected void onDraw(Canvas canvas) { 54 super.onDraw(canvas); 55 56 //①獲取系統時間 57 getCurrentTime(); 58 59 //②當前時間時分秒分別所占的份數(角度),即為上面rr,rr2所得到的每份的角度乘以獲得的時間 60 Double secondAngle = rr * startSecond; 61 Double minuteAngle = rr * startMinute; 62 Double hourAngle = rr2 * startHour; 63 64 //③利用三角函數計算分別計算出,時分秒三針所在的坐標點,坐標原點預設在手機屏幕左上角 65 float sencondScale = 5 * arcRa / 6;//秒針長度 66 float minuteScale = 3 * arcRa / 4;//分針長度 67 float hourScale = arcRa / 2;//時針長度 68 secondStartPoint.x = (float) (arcRa + sencondScale * Math.sin(secondAngle)); 69 secondStartPoint.y = (float) (arcRa - sencondScale * Math.cos(secondAngle)); 70 minuteStartPoint.x = (float) (arcRa + minuteScale * Math.sin(minuteAngle)); 71 minuteStartPoint.y = (float) (arcRa - minuteScale * Math.cos(minuteAngle)); 72 hourStartPoint.x = (float) (arcRa + hourScale * Math.sin(hourAngle)); 73 hourStartPoint.y = (float) (arcRa - hourScale * Math.cos(hourAngle)); 74 75 //④畫圓,通過獲取寬高算出最短一邊作為直徑,坐標原點預設在手機屏幕左上角 76 canvas.drawCircle(arcRa, arcRa, arcRa, paint); 77 78 //⑤圍繞圓形繪製刻度,坐標原點預設在手機屏幕左上角 79 int itime = 12;//長的刻度要顯示的數字,這裡從12點刻度開始順時針繪製 80 for (int i = 0; i < 60; i++) {///2π圓形分成60份,一秒鐘與一分鐘,所以要繪製60次,這裡是從0到59 81 float x1, y1, x2, y2;//刻度的兩端的坐標即起始於結束的坐標 82 float scale;//每個刻度離圓心的最近端坐標點到圓心的距離 83 Double du = rr * i;//當前所占的角度 84 Double sinx = Math.sin(du);//該角度的sin值 85 Double cosy = Math.cos(du);//該角度的cos值 86 x1 = (float) (arcRa + arcRa * sinx);//以預設坐標系通過三角函數算出刻度離圓心最遠的端點的x軸坐標 87 y1 = (float) (arcRa - arcRa * cosy);//以預設坐標系通過三角函數算出刻度離圓心最遠的端點的y軸坐標 88 if (i % 5 == 0) {//篩選刻度長度 89 scale = 5 * arcRa / 6;//長刻度繪製,刻度離圓心的最近端坐標點到圓心的距離,這裡取半徑的五分之六的長度,可以通過情況來定 90 91 //繪製長刻度上的數字1~12 92 String number = itime + "";//當前數字變為String類型 93 itime++;//數字加1 94 if (itime > 12) {//如果大於數字12,重置為1 95 itime = 1; 96 } 97 float numScale = 4 * arcRa / 5;//數字離圓心的距離,這裡取半徑的五分之四的長度,可以通過情況來定 98 float x3 = (float) (arcRa + numScale * sinx);//以預設坐標系通過三角函數算出x軸坐標 99 float y3 = (float) (arcRa - numScale * cosy);//以預設坐標系通過三角函數算出x軸坐標 100 paint.getTextBounds(number, 0, number.length(), textBound);//獲取每個數字被全部包裹的最小的矩形邊框數值 101 102 //繪製數字,通過x3,y3根據文字最小包裹矩形邊框數值進行繪製點調整 103 canvas.drawText(number, x3 - textBound.width() / 2, y3 + textBound.height() / 2, paint); 104 105 } else { 106 scale = 9 * arcRa / 10;//短刻度繪製,這裡取半徑的十分之六九的長度,可以通過情況來定 107 } 108 x2 = (float) (arcRa + scale * sinx);//以預設坐標系通過三角函數算出該刻度離圓心最近的端點的x軸坐標 109 y2 = (float) (arcRa - scale * cosy);//以預設坐標系通過三角函數算出該刻度離圓心最近的端點的y軸坐標 110 canvas.drawLine(x1, y1, x2, y2, paint);//通過兩端點繪製刻度 111 } 112 113 //⑥繪製時、分、秒針,坐標原點預設在手機屏幕左上角 114 canvas.drawLine(arcRa, arcRa, secondStartPoint.x, secondStartPoint.y, paint); 115 canvas.drawLine(arcRa, arcRa, minuteStartPoint.x, minuteStartPoint.y, paint); 116 canvas.drawLine(arcRa, arcRa, hourStartPoint.x, hourStartPoint.y, paint); 117 118 postInvalidateDelayed(1000);//每秒刷新一次 119 } 120 121 private void getCurrentTime() { 122 long time = System.currentTimeMillis();//獲取時間 123 Calendar mCalendar = Calendar.getInstance(); 124 mCalendar.setTimeInMillis(time); 125 startHour = mCalendar.get(Calendar.HOUR);//獲取小時,12小時制 126 startMinute = mCalendar.get(Calendar.MINUTE);//獲取分鐘 127 startSecond = mCalendar.get(Calendar.SECOND);//獲取秒 128 } 129 }