Android自定义控件练手——简单的时钟
首先这应该是一个老生常谈的设计了,但是毕竟身为小白的自己都没动手做过,不动手怎么提高自己呢,所以在这梅林沉船闲暇之际,我就把我的设计流程与思路记录下来。首先来看看效果图吧:
如上图就是一个简单并没有美化过的时钟,接下来我就来讲讲我的设计流程与思路。
一.首先继承view重写里面的onDraw方法。
我们要搭建好了画布才能开始在里面画画,而onDraw方法中的canvas当然就是起到画布的作用。
public class MyClockView extends View { public MyClockView(Context context) {
super(context);
init();//初始化的方法
} public MyClockView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
} public MyClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
} public void init() { } @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
} }
二.准备需要用到的工具。
要画一个时钟当然首先得要有笔才行,第一步上面我们得到了画布,现在我们还需要一个paint的画笔。似乎绘图用的工具就这么多了,一张画布,一支笔,那么让我们想想还需要用到些什么变量,我就先把时钟结构拆分成了,时、分、秒针,一个圆圈框和时钟的刻度与数字,但是这些能代表些什么deep♂dark♂fantastic的呢,再让我们想想这里一般绘图当然要和坐标挂钩,那就都变成二维坐标吧,时分秒针都是直线,就是画线,圆框就是画圆要知道圆心与半径,刻度也是画线,数字就是写字,拆分为了,时分秒针两端的坐标,圆心与半径,刻度两端的坐标,数字绘制开始的坐标。
三.坐标绘制的算法分析。
我们应该是都知道要想根据坐标绘制就要先知道它的原点在哪,一般默认情况下它的原点定在屏幕左上角,并且y轴下半部为正半轴,上半部为负半轴。如图:
根据上面图的坐标系,我们现在要画一个圆形,里面再画刻度,再画时分秒针,首先我们要确定圆心在哪,我按照习惯以控件长宽最短的一边为直径来定圆心的坐标,我在刚才继承view之后重写onSizeChanged方法(这个方法是当宽高放生变化和第一次会执行)做获取半径长:
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;//获得宽度
mHeight = h;//获得高度 //以最短的一边为所要绘制圆形的直径
if (mWidth > mHeight) {
arcRa = mHeight / 2;//以最短的一边算出半径
} else {
arcRa = mWidth / 2;//以最短的一边算出半径
}
super.onSizeChanged(w, h, oldw, oldh);
}
这样在默认坐标系中,圆心坐标即为(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啦。
嗨呀,终于可以开始画了,不过在开始绘制指针之前,先要获取时间才行:
private void getCurrentTime() {
long time = System.currentTimeMillis();//获取时间
Calendar mCalendar = Calendar.getInstance();
mCalendar.setTimeInMillis(time);
startHour = mCalendar.get(Calendar.HOUR);//获取小时,12小时制
startMinute = mCalendar.get(Calendar.MINUTE);//获取分钟
startSecond = mCalendar.get(Calendar.SECOND);//获取秒
}
这里我们获取到的时间其实就是所占的份数,分钟与秒都是总共60份,小时为12份。
先画圆与刻度:
//画圆,通过获取宽高算出最短一边作为直径,坐标原点默认在手机屏幕左上角
canvas.drawCircle(arcRa, arcRa, arcRa, paint); //围绕圆形绘制刻度,坐标原点默认在手机屏幕左上角
for (int i = 0; i < 60; i++) {///2π圆形分成60份,一秒钟与一分钟,所以要绘制60次,这里是从0到59
float x1, y1, x2, y2;//刻度的两端的坐标即起始于结束的坐标
float scale;//每个刻度离圆心的最近端坐标点到圆心的距离
Double du = rr * i;//当前所占的角度
Double sinx = Math.sin(du);//该角度的sin值
Double cosy = Math.cos(du);//该角度的cos值
x1 = (float) (arcRa + arcRa * sinx);//以默认坐标系通过三角函数算出刻度离圆心最远的端点的x轴坐标
y1 = (float) (arcRa - arcRa * cosy);//以默认坐标系通过三角函数算出刻度离圆心最远的端点的y轴坐标
if (i % 5 == 0) {//筛选刻度长度
scale = 5 * arcRa / 6;//长刻度绘制,刻度离圆心的最近端坐标点到圆心的距离,这里取半径的五分之六的长度,可以通过情况来定
} else {
scale = 9 * arcRa / 10;//短刻度绘制,这里取半径的十分之六九的长度,可以通过情况来定
}
x2 = (float) (arcRa + scale * sinx);//以默认坐标系通过三角函数算出该刻度离圆心最近的端点的x轴坐标
y2 = (float) (arcRa - scale * cosy);//以默认坐标系通过三角函数算出该刻度离圆心最近的端点的y轴坐标
canvas.drawLine(x1, y1, x2, y2, paint);//通过两端点绘制刻度
}
然后开始绘制时分秒指针:
//利用三角函数计算分别计算出,时分秒三针所在的坐标点,坐标原点默认在手机屏幕左上角
float sencondScale = 5 * arcRa / 6;//秒针长度
float minuteScale = 3 * arcRa / 4;//分针长度
float hourScale = arcRa / 2;//时针长度
secondStartPoint.x = (float) (arcRa + sencondScale * Math.sin(secondAngle));
secondStartPoint.y = (float) (arcRa - sencondScale * Math.cos(secondAngle));
minuteStartPoint.x = (float) (arcRa + minuteScale * Math.sin(minuteAngle));
minuteStartPoint.y = (float) (arcRa - minuteScale * Math.cos(minuteAngle));
hourStartPoint.x = (float) (arcRa + hourScale * Math.sin(hourAngle));
hourStartPoint.y = (float) (arcRa - hourScale * Math.cos(hourAngle));
//绘制时、分、秒针,坐标原点默认在手机屏幕左上角
canvas.drawLine(arcRa, arcRa, secondStartPoint.x, secondStartPoint.y, paint);
canvas.drawLine(arcRa, arcRa, minuteStartPoint.x, minuteStartPoint.y, paint);
canvas.drawLine(arcRa, arcRa, hourStartPoint.x, hourStartPoint.y, paint);
其实上面都是一个画线条的过程,也就是知道两点坐标drawLine的过程。接下来绘制数字,找到需要绘制地点的坐标,我们在长刻度上绘制小时数,一圈12个小时,那就在刚才上面绘制长刻度里面加上:
//绘制长刻度上的数字1~12
String number = itime + "";//当前数字变为String类型
itime++;//数字加1
if (itime > 12) {//如果大于数字12,重置为1
itime = 1;
}
float numScale = 4 * arcRa / 5;//数字离圆心的距离,这里取半径的五分之四的长度,可以通过情况来定
float x3 = (float) (arcRa + numScale * sinx);//以默认坐标系通过三角函数算出x轴坐标
float y3 = (float) (arcRa - numScale * cosy);//以默认坐标系通过三角函数算出x轴坐标
paint.getTextBounds(number, 0, number.length(), textBound);//获取每个数字被全部包裹的最小的矩形边框数值 //绘制数字,通过x3,y3根据文字最小包裹矩形边框数值进行绘制点调整
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
public class MyClockView extends View {
private Paint paint;//画笔
private int mainColor = Color.parseColor("#000000");//画笔颜色
private float mWidth, mHeight;//视图宽高
private float arcRa = 0;//圆半径
private Double rr = 2 * Math.PI / 60;//2π即360度的圆形分成60份,一秒钟与一分钟
private Double rr2 = 2 * Math.PI / 12;//2π圆形分成12份,圆形显示12个小时的刻度
private PointF secondStartPoint, minuteStartPoint, hourStartPoint;//秒,分,时的坐标点
private int startSecond, startMinute, startHour;//初始化时秒,分,时获取的系统时间
private Rect textBound = new Rect();//字体被全部包裹的最小的矩形边框 public MyClockView(Context context) {
super(context);
init();
} public MyClockView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
} public MyClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;//获得宽度
mHeight = h;//获得高度 //以最短的一边为所要绘制圆形的直径
if (mWidth > mHeight) {
arcRa = mHeight / 2;//以最短的一边算出半径
} else {
arcRa = mWidth / 2;//以最短的一边算出半径
}
super.onSizeChanged(w, h, oldw, oldh);
} public void init() {
paint = new Paint();//初始化画笔
paint.setColor(mainColor);//设置颜色
// paint.setAntiAlias(true);//抗锯齿(性能影响)
paint.setStyle(Paint.Style.STROKE);//设置画笔
paint.setTextSize(45);//设置字体大小
secondStartPoint = new PointF(arcRa, 0);//初始化坐标点
hourStartPoint = new PointF(arcRa, 0);
minuteStartPoint = new PointF(arcRa, 0);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); //①获取系统时间
getCurrentTime(); //②当前时间时分秒分别所占的份数(角度),即为上面rr,rr2所得到的每份的角度乘以获得的时间
Double secondAngle = rr * startSecond;
Double minuteAngle = rr * startMinute;
Double hourAngle = rr2 * startHour; //③利用三角函数计算分别计算出,时分秒三针所在的坐标点,坐标原点默认在手机屏幕左上角
float sencondScale = 5 * arcRa / 6;//秒针长度
float minuteScale = 3 * arcRa / 4;//分针长度
float hourScale = arcRa / 2;//时针长度
secondStartPoint.x = (float) (arcRa + sencondScale * Math.sin(secondAngle));
secondStartPoint.y = (float) (arcRa - sencondScale * Math.cos(secondAngle));
minuteStartPoint.x = (float) (arcRa + minuteScale * Math.sin(minuteAngle));
minuteStartPoint.y = (float) (arcRa - minuteScale * Math.cos(minuteAngle));
hourStartPoint.x = (float) (arcRa + hourScale * Math.sin(hourAngle));
hourStartPoint.y = (float) (arcRa - hourScale * Math.cos(hourAngle)); //④画圆,通过获取宽高算出最短一边作为直径,坐标原点默认在手机屏幕左上角
canvas.drawCircle(arcRa, arcRa, arcRa, paint); //⑤围绕圆形绘制刻度,坐标原点默认在手机屏幕左上角
int itime = 12;//长的刻度要显示的数字,这里从12点刻度开始顺时针绘制
for (int i = 0; i < 60; i++) {///2π圆形分成60份,一秒钟与一分钟,所以要绘制60次,这里是从0到59
float x1, y1, x2, y2;//刻度的两端的坐标即起始于结束的坐标
float scale;//每个刻度离圆心的最近端坐标点到圆心的距离
Double du = rr * i;//当前所占的角度
Double sinx = Math.sin(du);//该角度的sin值
Double cosy = Math.cos(du);//该角度的cos值
x1 = (float) (arcRa + arcRa * sinx);//以默认坐标系通过三角函数算出刻度离圆心最远的端点的x轴坐标
y1 = (float) (arcRa - arcRa * cosy);//以默认坐标系通过三角函数算出刻度离圆心最远的端点的y轴坐标
if (i % 5 == 0) {//筛选刻度长度
scale = 5 * arcRa / 6;//长刻度绘制,刻度离圆心的最近端坐标点到圆心的距离,这里取半径的五分之六的长度,可以通过情况来定 //绘制长刻度上的数字1~12
String number = itime + "";//当前数字变为String类型
itime++;//数字加1
if (itime > 12) {//如果大于数字12,重置为1
itime = 1;
}
float numScale = 4 * arcRa / 5;//数字离圆心的距离,这里取半径的五分之四的长度,可以通过情况来定
float x3 = (float) (arcRa + numScale * sinx);//以默认坐标系通过三角函数算出x轴坐标
float y3 = (float) (arcRa - numScale * cosy);//以默认坐标系通过三角函数算出x轴坐标
paint.getTextBounds(number, 0, number.length(), textBound);//获取每个数字被全部包裹的最小的矩形边框数值 //绘制数字,通过x3,y3根据文字最小包裹矩形边框数值进行绘制点调整
canvas.drawText(number, x3 - textBound.width() / 2, y3 + textBound.height() / 2, paint); } else {
scale = 9 * arcRa / 10;//短刻度绘制,这里取半径的十分之六九的长度,可以通过情况来定
}
x2 = (float) (arcRa + scale * sinx);//以默认坐标系通过三角函数算出该刻度离圆心最近的端点的x轴坐标
y2 = (float) (arcRa - scale * cosy);//以默认坐标系通过三角函数算出该刻度离圆心最近的端点的y轴坐标
canvas.drawLine(x1, y1, x2, y2, paint);//通过两端点绘制刻度
} //⑥绘制时、分、秒针,坐标原点默认在手机屏幕左上角
canvas.drawLine(arcRa, arcRa, secondStartPoint.x, secondStartPoint.y, paint);
canvas.drawLine(arcRa, arcRa, minuteStartPoint.x, minuteStartPoint.y, paint);
canvas.drawLine(arcRa, arcRa, hourStartPoint.x, hourStartPoint.y, paint); postInvalidateDelayed(1000);//每秒刷新一次
} private void getCurrentTime() {
long time = System.currentTimeMillis();//获取时间
Calendar mCalendar = Calendar.getInstance();
mCalendar.setTimeInMillis(time);
startHour = mCalendar.get(Calendar.HOUR);//获取小时,12小时制
startMinute = mCalendar.get(Calendar.MINUTE);//获取分钟
startSecond = mCalendar.get(Calendar.SECOND);//获取秒
}
}
Android自定义控件练手——简单的时钟的更多相关文章
- Android自定义控件练手——波浪效果
这一次要绘制出波浪效果,也是小白的我第一次还望轻喷.首先当然是展示效果图啦: 一.首先来说说实现思路. 想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在 ...
- vue练手项目——桌面时钟
用vue实现一个简单的网页桌面时钟,主要包括时钟显示.计时.暂停.重置等几个功能. 效果图如下,页面刚进来的时候是一个时钟,时钟上显示的时.分.秒为当前实际时间,点击计时器按钮后,页面变成一个计时器, ...
- NEXYS 3开发板练手--LED与数码管时钟
做科研的时候从学校拿到一块基于Xilinx公司Spartan-6主芯片的FPGA开发板,因为之前一直在用Altera公司的FPGA,一开始接触它还真有点不太习惯.但毕竟核心的东西还是不会变的,于是按照 ...
- 练手WPF(一)——模拟时钟与数字时钟的制作(上)
一.Visual Studio创建一个WPF项目. 简单调整一下MainWindow.xaml文件.主要使用了两个Canvas控件,分别用于显示模拟和数字时钟,命名为AnalogCanvas.digi ...
- Xamarin入门,开发一个简单的练手APP
之前周末用Xamarin练手做了个简单APP,没有啥逻辑基本就是个界面架子,MVVM的简单使用,还有Binding,Command的简单使用,还有一个稍微复杂点两个界面交互处理(子页面新增后关闭,父页 ...
- 简单的node爬虫练手,循环中的异步转同步
简单的node爬虫练手,循环中的异步转同步 转载:https://blog.csdn.net/qq_24504525/article/details/77856989 看到网上一些基于node做的爬虫 ...
- 简单的ssm练手联手项目
简单的ssm练手联手项目 这是一个简单的ssm整合项目 实现了汽车的品牌,价格,车型的添加 ,修改,删除,所有数据从数据库中拿取 使用到了jsp+mysql+Mybatis+spring+spring ...
- Android中Tomcat的简单配置和使用
因为学Android已经有一段时间了,但是在学校,服务器方面是个短板啊,没有专门的服务器拿给我们学生练手,所以只有自己找办法了.当然,Tomcat就是不二的选择了. 在网上看了看资料,还是觉得自己记录 ...
- Android自定义控件实战——滚动选择器PickerView
转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38513301 手机里设置闹钟需要选择时间,那个选择时间的控件就是滚动选择器, ...
随机推荐
- 八数码 Java实现
参考http://blog.csdn.net/helloworld10086/article/details/41853389 package com.EightNumber.view; import ...
- 使用jquery插件实现图片延迟加载--懒加载技术
原文链接:http://www.cnblogs.com/lei2007/archive/2013/05/31/3110725.html 感谢作者.以下为原文,备忘仅供自己学习. 第一:lazyLoad ...
- winform中的ComboBox同时设置text和value的方法
winform中的ComboBox不能像webform中的dropdownlist控件一样,在属性中可以同时设置text和value值,可以通过编写一个新类来实现这个功能. 1.首先在form1中添加 ...
- BMFont使用图片自定义字体(无需字体文件)
网上搜BMFont做字体,很多都是从一个字体文件读取,然后选择需要的字,然后保存成图片文字,这个对于一般的文字的确很实用,因为Unity本身不支持中文,所以只好这样了. 但是做过游戏的都知道,策划总是 ...
- AQS(AbstractQueuedSynchronizer)介绍-01
1.概述 AQS( AbstractQueuedSynchronizer ) 是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很容易并且高效地构造出来.如: ReentrantLock 和 ...
- sql server 2008 r2安装
选择功能(好像报错了-下次重装系统测试)
- C++基础之C++编译调试
C++程序的实现(预处理,编译,连接)Linux平台编译gcc和g++都是GNU的编译器.1.对于.c后缀的文件,gcc把它当做是C程序:g++当做是C++程序:2.对于.cpp后缀的文件,gcc和 ...
- Mysql-6-数据类型和运算符
1.mysql数据类型 (1)数值数据类型:包括整数类型tinyint.smallint.mediumint.int.bigint,浮点小数类型float和double,定点小数类型decimal. ...
- [Xcode 实际操作]四、常用控件-(17)为MKMapView地图上显示提示框
目录:[Swift]Xcode实际操作 本文将演示当点击地图上的标注圆点时,弹出信息窗口. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] import UIKit ...
- C# Stack堆栈的使用方法
堆栈(Stack)代表了一个后进先出的对象集合.当您需要对各项进行后进先出的访问时,则使用堆栈.当您在列表中添加一项,称为推入元素,当您从列表中移除一项时,称为弹出元素. Stack 类的方法和属性 ...