Android自定義控件練手——簡單的時鍾


    首先這應該是一個老生常談的設計了,但是畢竟身為小白的自己都沒動手做過,不動手怎么提高自己呢,所以在這梅林沉船閑暇之際,我就把我的設計流程與思路記錄下來。首先來看看效果圖吧:

    如上圖就是一個簡單並沒有美化過的時鍾,接下來我就來講講我的設計流程與思路。

一.首先繼承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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM