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 }

原文地址:https://www.cnblogs.com/SteinsGateZero/p/8436025.html

时间: 2024-10-18 02:39:28

Android自定义控件练手——简单的时钟的相关文章

Android自定义控件练手——波浪效果

这一次要绘制出波浪效果,也是小白的我第一次还望轻喷.首先当然是展示效果图啦: 一.首先来说说实现思路. 想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在demo里这种方法也实现了),肯定要绘制一个静态的波,然后通过不断的对它平移刷新,这样最简单的波浪效果就有了,如果再给它加一个比它提前一定周期的波一起平移,那不是波浪效果的层次就有了. 二.绘制. 首先要绘制一个静态的波形图,嗨呀说来简单但是怎么画呢,不要慌先看下面这张丑图: 通过上面的图我们发现曲

【练手】博客园 Android 客户端 (码厩 - Cotable)

最近一段时间正好有点空,就利用这点闲余时间学习一下 Android的开发(包括Material Design),因为平时也会经常浏览博客园里大家写得技术交流分享文章,所以干脆称练手的几乎,做一个博客园的手机客户端,之前也稍微收集了一下博客园的api, 由于好像博客园没有官方api来着,所以参考使用了一些其他大牛的api, 如果有时间的话,打算后期自己利用python + django包装一下博客园的web api吧,可能把博客园抓取的内容重构一下,适应移动端的阅读,尤其是移动端阅读含有源代码的博

PHP练手:日历(代码简单,扩展容易)

抽空写了个日历程序,只注重功能和实现的思路,所以代码和功能都比较简单,但是理解和扩展也比较容易. show()函数用来显示日历,你可以修改show()函数,通过传值的方式来实现显示不同的年月. <?php class Calendar{ public $weekarray = array('星期日','星期一','星期二','星期三','星期四','星期五','星期六'); public $firstDay = '';//当月第一天 public $firstNum = '';//返回当月第一天

Android中Tomcat的简单配置和使用

因为学Android已经有一段时间了,但是在学校,服务器方面是个短板啊,没有专门的服务器拿给我们学生练手,所以只有自己找办法了.当然,Tomcat就是不二的选择了. 在网上看了看资料,还是觉得自己记录下来比较好. 因为我是学Android的,所以jdk什么的已配置好了.如果不知道,请看<java的环境变量配置> 首先我们先要下载Tomcat:http://tomcat.apache.org/(请自行选择版本),我用的是Tomcat 7.0.55. 我下载下来是一个压缩包,选择好路径后,进行解压

练手小项目(2)-生活小助手--周公解梦

第一篇 练手小项目(2)-生活小助手--身份证查询 第二篇 练手小项目(2)-生活小助手--星座运势查询 我在想就是第三个药品查询要不要写出来,因为布局还在讨论用什么展示,因为药品有很多展示,我也不知道用什么展示. 这是一个很纠结的事情 我就先写第四个吧 周公解梦 其中代码有点错误我想用for循环进行判断返回数据有几个 但是总是失败,如果有看本篇贴子,解决了,给我留个言,在这篇帖子我只显示一个结果 布局跟简单的说 一个Edittext 获取数据,然后button进行数据提取发送到服务器 返回的数

Android自定义控件实战——滚动选择器PickerView

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38513301 手机里设置闹钟需要选择时间,那个选择时间的控件就是滚动选择器,前几天用手机刷了MIUI,发现自带的那个时间选择器效果挺好看的,于是就自己仿写了一个,权当练手.先来看效果: 效果还行吧?实现思路就是自定义一个PickerView,单独滚动的是一个PickerView,显然上图中有分和秒的选择所以在布局里用了两个PickerView.由于这里不涉及到text的点击

练手小项目(2)-生活小助手--星座运势查询

上一篇内容 练手小项目(2)-生活小助手 今天星期一.趁着中午的歇息时间把 第二个写出来 星座运势,近期看看极客学院 用聚合数据做了天气预报的视频教程,不好评价他.看他在后面的代码变更那么大,我就知道,后面肯定做不下去,于是.就改代码了.代码变更那么大,有几个人会去理解,还不如我自己写................ 先看布局 点击去就是一个spinner 用几个textview显示查询内容   布局有点丑,主要是给别人做功能,UI我就不考虑 关于UI  我还是要贴下代码.假设你有想法就把他美化

自定义控件其实很简单1/6

尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵权必究! 炮兵镇楼 上一节我们粗略地讲了下如何去实现我们的View并概述了View形成动画的基本原理,这一节我们紧跟上一节的步伐来深挖如何去绘制更复杂的View! 通过上一节的学习我们了解到什么是画布Canvas什么是画笔Paint,并且学习了如何去设置画笔的属性如何在画布上画一个圆,然而,画笔的属性并非仅仅就设置个颜色.大小那么简单而画布呢肯定也不单

[移动开发] Android自定义控件系列六:自定义ViewGroup(一)实现ViewPager效果

今天我们开始新的Android自定组件旅程,下面一个内容是如何自定义一个ViewGoup,之前我们已经通过几篇博文已经了解了自定义view的基本写法,如果有不了解的同学,可以参看下面专栏中的文章:Android自定义控件. 这次同样也是通过一个例子来说明要如何自定义一个ViewGroup,最终目标就是要实现一个类ViewPager功能的ViewGroup. 我们先来看看最终效果: 对于系统的ViewGroup我们已经是十分熟悉了,最常用的LinearLayout和RelativeLayout几乎