android自定义View之钟表诞生记
很多筒子觉得自定义View是高手的象征,其实不然。大家觉得自定义View难很多情况下可能是因为自定义View涉及到了太多的类和API,把人搞得晕乎乎的,那么今天我们就从最简单的绘图API开始,带大家来一步一步深入自定义View的世界。
先来看看我们今天要实现的一个效果图:
整个效果很简单,就是在屏幕上显示一个钟表,该钟表可以自动走动。
OK,那就开始动工吧。
1.准备工作
首先,要实现这个时钟,我得继承自View来自己绘制时钟,因为这种效果没有办法继承已有控件去完善功能。然后我们来看看我们这里需要哪些变量?在这篇博客中我暂时不打算介绍自定义属性以及View的测量,这里我只想介绍绘图API,所以View的大小以及钟表表针的颜色等我都暂时先给一个固定的值。OK,那么我们需要的变量主要就是下面几个:
/**
* 绘制表盘的画笔
*/
private Paint circlePaint; /**
* 绘制表盘数字
*/
private Paint numPaint;
/**
* 绘制表心
*/
private Paint dotPaint;
/**
* 时针
*/
private Paint hourPaint;
/**
* 分针
*/
private Paint minutePaint;
/**
* 秒针
*/
private Paint secondPaint;
/**
* View宽度,默认256dp
*/
private int width;
/**
* View高度,默认256dp
*/
private int height;
/**
* 日历类,用来获取当前时间
*/
private Calendar calendar;
/**
* 当前时针颜色
*/
private int hourColor;
/**
* 当前分针颜色
*/
private int minuteColor;
/**
* 当前秒针颜色
*/
private int secondColor;
/**
* 时针宽度
*/
private int hourWidth;
/**
* 分针宽度
*/
private int minuteWidth;
/**
* 秒针宽度
*/
private int secondWidth;
一共就是这么多个变量。
2.关于构造方法
大家看到,当我继承View之后,系统要求我实现它的构造方法,构造方法主要有四个,如下:
1.
public ClockView(Context context)
该构造方法是当我在Java代码中new一个View的时候调用的。
2.
public ClockView(Context context, AttributeSet attrs)
该构造方法是当我在布局文件中添加一个View时调用的。
3.
public ClockView(Context context, AttributeSet attrs, int defStyleAttr)
很多筒子看到第三个参数defStyleAttr之后,误以为如果我在布局文件中写了style就会调用该构造方法,其实不然,这个构造方法系统并不会自己去调用(大家有兴趣可以自己写一个style,然后在这个方法中打印日志,看看该方法究竟会不会调用),要由我们自己显式调用(可以在第二个构造方法中调用)。那么这里的defStyleAttr究竟是什么意思呢?正如这个参数的字面意思,它是我们为自定义的View指定的一个默认样式。(后面博客我们再来详细说一下这个方法)。
另外,还有一个高版本使用的构造方法,我们这里暂不做介绍。
一般情况下,我们需要在构造方法中完成一些初始化的操作,比如读取在XML文件中定义的属性,或者初始化画笔等等,因为我们的控件既有可能是通过Java代码实例化的,也有可能是在布局文件中通过xml添加的。如前所述,如果我们在Java代码中初始化控件,那么将调用第一个构造方法,如果我们在xml布局文件中添加控件,那么将调用第二个构造方法。这时问题来了,那我们的初始化控件的方法应该写在那个构造方法中呢?你可以按下面这种方式来写:
public ClockView(Context context) {
super(context);
initView();
}
public ClockView(Context context, AttributeSet attrs) {
super(context,attrs);
initView();
}
在两个构造方法中分别调用初始化的方法,这种方式没有问题,但是显得你的思路没有条理,参考Android提供的其他控件的源码,我建议在初始化控件时按照下面这种方式来写:
//在代码中创建控件时调用
public ClockView(Context context) {
this(context, null);
} //在布局文件中创建View时调用
public ClockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
虽然结果都一样,但是下面这种写法显得你思路很清晰。
3.初始化控件
我们在准备工作中定义了许多变量,包括钟表的颜色,指针的颜色等等许多变量,那么接下来我们需要在initView这个方法中来初始化这些变量,以供下一步使用,OK,我们来看一看初始化代码:
private void initView() {
//获取当前时间的实例
calendar = Calendar.getInstance();
//时钟默认宽高
width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics());
height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics());
//初始化表针的颜色
hourColor = Color.RED;
minuteColor = Color.GREEN;
secondColor = Color.BLUE;
//初始化表针的宽度
hourWidth = 8;
minuteWidth = 5;
secondWidth = 2;
//初始化各种画笔
circlePaint = new Paint();
//去锯齿
circlePaint.setAntiAlias(true);
//设置画笔颜色
circlePaint.setColor(Color.GREEN);
//设置画笔style为描边
circlePaint.setStyle(Paint.Style.STROKE);
//设置描边的宽度
circlePaint.setStrokeWidth(6);
dotPaint = new Paint();
dotPaint.setAntiAlias(true);
dotPaint.setColor(Color.RED);
dotPaint.setStyle(Paint.Style.FILL);
numPaint = new Paint();
numPaint.setColor(Color.RED);
numPaint.setAntiAlias(true);
//文本对齐方式
numPaint.setTextAlign(Paint.Align.CENTER);
hourPaint = new Paint(); hourPaint.setColor(hourColor);
hourPaint.setStyle(Paint.Style.FILL);
hourPaint.setStrokeWidth(hourWidth);
minutePaint = new Paint();
minutePaint.setColor(minuteColor);
minutePaint.setStyle(Paint.Style.FILL);
minutePaint.setStrokeWidth(minuteWidth);
secondPaint = new Paint();
secondPaint.setColor(secondColor);
secondPaint.setStyle(Paint.Style.FILL);
secondPaint.setStrokeWidth(secondWidth);
}
首先是获得一个当前时间的实例,因为我需要根据手机上的时间来显示钟表的时间,其次就是对表针的各种属性和画笔进行初始化,这里的东西都很简单,我就不再一一细说,大家看代码注释,相信都能看得懂。
4.绘制钟表
上面所有的工作做完之后,接下来就是绘制钟表了,绘制工作我们放在了onDraw方法中执行,在自定义控件时,如果该控件是我们继承自View来实现的,那么基本上这个控件就是需要我们自己来绘制了。
OK,那我们来看看钟表的绘制吧。
钟表不算复杂,但是我们也需要一步一步来:
首先是绘制表盘,这个最简单:
//1.圆心X轴坐标,2.圆心Y轴坐标,3.半径,4.画笔
int radius = width / 2 - 10;
//画表盘
canvas.drawCircle(width / 2, height / 2, radius, circlePaint);
radius表示表盘的半径,通过drawCircle绘制一个圆环,四个参数分别是圆环的中心点坐标,圆环的半径以及绘制圆环的画笔。
圆环画好之后,那么接下来就是绘制表心了,也就是表盘正中心那个红色的圆心。
canvas.drawCircle(width / 2, height / 2, 15, dotPaint);
很简单吧。
OK,两个最简单的东东画完之后,那么接下来就是绘制表盘的时间刻度了,时间刻度的绘制除了数字之外,还有一个绿色的短线,我们一共要画十二个这种东西,那么这个要怎么绘制呢?思路有很多,你可以按照高中的数学知识,计算出每一个数字的坐标以及每一个短线起始位置和结束位置的坐标,然后绘制出来,毫无疑问,这种方式的计算量太大,那我们这里采取一个简单的方式:我每次只在十二点的那个位置上进行绘制,如果需要绘制一点,那么我把画布逆时针旋转30度到十二点的位置,然后画上1和一个短线之后再将画布顺时针旋转30度,如果是绘制2点,那么我把画布逆时针旋转60度到十二点的位置,然后绘制上2和一个短线,绘制完成之后再将画布顺时针旋转60度,思路就是这样,下面我们来看看代码:
for (int i = 1; i < 13; i++) {
//在旋转之前保存画布状态
canvas.save();
canvas.rotate(i * 30, width / 2, height / 2);
//1.2表示起点坐标,3.4表示终点坐标,5.画笔
canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 10, circlePaint);
//画表盘数字1.要绘制的文本,2.文本x轴坐标,3.文本基线,4.文本画笔
canvas.drawText(i + "", width / 2, height / 2 - radius + 22, numPaint);
//恢复画布状态
canvas.restore();
}
我用一个循环来绘制这十二个刻度,在每次旋转画布之前,我都通过一个canvas.save()方法来保存画布当前的状态,保存之后再对画布进行旋转操作,旋转完成之后就是画线画数字,这些都很简单,在绘制完成之后,我需要调用canvas的restore()方法,该方法可以让画布恢复到旋转之前的角度。一般情况下,canvas.save()方法和canvas.restore()方法都是成对出现的,这一点大家要注意。
OK,这些东西都绘制完成之后,接下来就该绘制表针了,表针的绘制思路和上面一样,也是先旋转表盘,然后绘制表针,绘制完成之后,再把表盘旋转回之前的状态。这里我就不再详细说明了,大家看代码:
//获得当前小时
int hour = calendar.get(Calendar.HOUR);
canvas.save();
//旋转屏幕
canvas.rotate(hour * 30, width / 2, height / 2);
//画时针
canvas.drawLine(width / 2, height / 2 + 20, width / 2, height / 2 - 90, hourPaint);
canvas.restore(); int minute = calendar.get(Calendar.MINUTE);
canvas.save();
canvas.rotate(minute * 6, width / 2, height / 2);
canvas.drawLine(width / 2, height / 2 + 30, width / 2, height / 2 - 110, minutePaint);
canvas.restore();
int second = calendar.get(Calendar.SECOND);
canvas.save();
canvas.rotate(second * 6, width / 2, height / 2);
canvas.drawLine(width / 2, height / 2 + 40, width / 2, height / 2 - 130, secondPaint);
canvas.restore();
OK,所有的事情做完之后,我们可以在布局文件中添加如下代码,来查看我们的工作做得怎么样:
<org.mobiletrain.clockview.ClockView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
添加完成之后,运行,不出意外的话,你的App上应该已经显示了一个时钟了,但是这个时钟是静止的,那么我们该怎么让时钟动起来呢?其实很简答,我们只需要每隔一秒重新获取calendar实例,然后重绘钟表即可,所以,在onDraw方法的开始和结束,我们还要分别添加如下两行代码:
1.开始处添加:
calendar = Calendar.getInstance();
这行代码用来获取最新的时间的实例
2.结束处添加:
postInvalidateDelayed(1000);
这行代码用来重绘钟表,不过重绘是在1秒之后。
OK,至此,我们的自定义钟表就完成了,完整的代码应该是这个样子:
/**
* Created by wangsong on 2016/3/29.
*/
public class ClockView extends View { /**
* 绘制表盘的画笔
*/
private Paint circlePaint; /**
* 绘制表盘数字
*/
private Paint numPaint;
/**
* 绘制表心
*/
private Paint dotPaint;
/**
* 时针
*/
private Paint hourPaint;
/**
* 分针
*/
private Paint minutePaint;
/**
* 秒针
*/
private Paint secondPaint;
/**
* View宽度,默认256dp
*/
private int width;
/**
* View高度,默认256dp
*/
private int height;
/**
* 日历类,用来获取当前时间
*/
private Calendar calendar;
/**
* 当前时针颜色
*/
private int hourColor;
/**
* 当前分针颜色
*/
private int minuteColor;
/**
* 当前秒针颜色
*/
private int secondColor;
/**
* 时针宽度
*/
private int hourWidth;
/**
* 分针宽度
*/
private int minuteWidth;
/**
* 秒针宽度
*/
private int secondWidth; //在代码中创建控件时调用
public ClockView(Context context) {
this(context, null);
} //在布局文件中创建View时调用
public ClockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
} private void initView() {
//获取当前时间的实例
calendar = Calendar.getInstance();
//时钟默认宽高
width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics());
height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics());
//初始化表针的颜色
hourColor = Color.RED;
minuteColor = Color.GREEN;
secondColor = Color.BLUE;
//初始化表针的宽度
hourWidth = 8;
minuteWidth = 5;
secondWidth = 2;
//初始化各种画笔
circlePaint = new Paint();
//去锯齿
circlePaint.setAntiAlias(true);
//设置画笔颜色
circlePaint.setColor(Color.GREEN);
//设置画笔style为描边
circlePaint.setStyle(Paint.Style.STROKE);
//设置描边的宽度
circlePaint.setStrokeWidth(6);
dotPaint = new Paint();
dotPaint.setAntiAlias(true);
dotPaint.setColor(Color.RED);
dotPaint.setStyle(Paint.Style.FILL);
numPaint = new Paint();
numPaint.setColor(Color.RED);
numPaint.setAntiAlias(true);
//文本对齐方式
numPaint.setTextAlign(Paint.Align.CENTER);
hourPaint = new Paint(); hourPaint.setColor(hourColor);
hourPaint.setStyle(Paint.Style.FILL);
hourPaint.setStrokeWidth(hourWidth);
minutePaint = new Paint();
minutePaint.setColor(minuteColor);
minutePaint.setStyle(Paint.Style.FILL);
minutePaint.setStrokeWidth(minuteWidth);
secondPaint = new Paint();
secondPaint.setColor(secondColor);
secondPaint.setStyle(Paint.Style.FILL);
secondPaint.setStrokeWidth(secondWidth);
} //绘制View
@Override
protected void onDraw(Canvas canvas) {
calendar = Calendar.getInstance();
//1.圆心X轴坐标,2.圆心Y轴坐标,3.半径,4.画笔
int radius = width / 2 - 10;
//画表盘
canvas.drawCircle(width / 2, height / 2, radius, circlePaint); canvas.drawCircle(width / 2, height / 2, 15, dotPaint);
for (int i = 1; i < 13; i++) {
//在旋转之前保存画布状态
canvas.save();
canvas.rotate(i * 30, width / 2, height / 2);
//1.2表示起点坐标,3.4表示终点坐标,5.画笔
canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 10, circlePaint);
//画表盘数字1.要绘制的文本,2.文本x轴坐标,3.文本基线,4.文本画笔
canvas.drawText(i + "", width / 2, height / 2 - radius + 22, numPaint);
//恢复画布状态
canvas.restore();
}
//获得当前小时
int hour = calendar.get(Calendar.HOUR);
canvas.save();
//旋转屏幕
canvas.rotate(hour * 30, width / 2, height / 2);
//画时针
canvas.drawLine(width / 2, height / 2 + 20, width / 2, height / 2 - 90, hourPaint);
canvas.restore(); int minute = calendar.get(Calendar.MINUTE);
canvas.save();
canvas.rotate(minute * 6, width / 2, height / 2);
canvas.drawLine(width / 2, height / 2 + 30, width / 2, height / 2 - 110, minutePaint);
canvas.restore();
int second = calendar.get(Calendar.SECOND);
canvas.save();
canvas.rotate(second * 6, width / 2, height / 2);
canvas.drawLine(width / 2, height / 2 + 40, width / 2, height / 2 - 130, secondPaint);
canvas.restore();
//每隔1秒重绘View,重绘会调用onDraw()方法
postInvalidateDelayed(1000);
}
}
以上。
android自定义View之钟表诞生记的更多相关文章
- android自定义View之NotePad出鞘记
现在我们的手机上基本都会有一个记事本,用起来倒也还算方便,记事本这种东东,如果我想要自己实现,该怎么做呢?今天我们就通过自定义View的方式来自定义一个记事本.OK,废话不多说,先来看看效果图. 整个 ...
- Android自定义View之ProgressBar出场记
关于自定义View,我们前面已经有三篇文章在介绍了,如果筒子们还没阅读,建议先看一下,分别是android自定义View之钟表诞生记.android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检 ...
- android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
我们的手机通讯录一般都有这样的效果,如下图: OK,这种效果大家都见得多了,基本上所有的android手机通讯录都有这样的效果.那我们今天就来看看这个效果该怎么实现. 一.概述 1.页面功能分析 整体 ...
- Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程
转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...
- Android自定义View 画弧形,文字,并增加动画效果
一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类 B ...
- (转)[原] Android 自定义View 密码框 例子
遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...
- Android 自定义View合集
自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...
- Android 自定义View (五)——实践
前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...
- Android 自定义 view(四)—— onMeasure 方法理解
前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...
随机推荐
- Oracle EBS 预警系统管理
本章主要讲述配置和设置Oracle EBS预警系统管理, 它比较方便和及时发用户或系统对数据库操作情况.下面讲一操作步聚: 1.预警系统管理-->系统-->选项 名称"Unix ...
- bzoj1976
终于忙完期末考试了,即将进入愉快的暑假(虽然暑假作业奇多,但好歹终于能有大量时间刷题了) 先把上次新一类最小割留下的一道题目A了复习一下: 题目看起来很复杂,实际上和bzoj2132是同一个类型的 用 ...
- wildfly-9.0.2 web项目部署详细步骤
一.配置操作系统环境变量 JAVA_HOME = C:\Program Files (x86)\Java\jdk1.7.0_67 JBOSS_HOME = F:\server\wildfly-9.0. ...
- keychain中我的证书与证书, p12与pem, apns, 推送
如果在A电脑上生成 的apns, 到B电脑上导入此文件时, 在 "我的证书"中找不到, 要去"证书"才能找到, 因为 创建时的csr不是本机生成的. 那这样的话 ...
- HTTP缓存是如何实现
浏览器是如何知道使用缓存的,其实这都是通过http中,浏览器将最后修改时间发送请求给web服务器,web服务器收到请求后跟服务器上的文档最后修改的时间对比,如果web服务器上最新文档修改时间小于或者等 ...
- C# GC.Collect()
用C#写了一个运用ICE组件进行接口通信的服务程序,程序运行很正常,可是在客户端调用ICE接口时出现了大量的数据丢失,而且偶尔还通信不上,服务端最明显的现象就是telnet服务的通信端口时不通(cmd ...
- ashx文件结合ajax使用(返回json数据)
ashx文件返回json数据: public void ProcessRequest(HttpContext context) { context.Response.ContentType = &qu ...
- ZOJ3732 Graph Reconstruction Havel-Hakimi定理
分析: 给定一个非负整数序列{dn},若存在一个无向图使得图中各点的度与此序列一一对应,则称此序列可图化. 进一步,若图为简单图,则称此序列可简单图化 (来自百度百科) 可简单图化的判定可以用Have ...
- [Tommas] Web测试中,各类web控件测试点总结
一 .界面检查 进入一个页面测试,首先是检查title,页面排版,字段等,而不是马上进入文本框校验 1.页面名称title是否正确 2.当前位置是否可见 您的位置:xxx>xxxx 3.文字格 ...
- 从打车软件你能想到多少?盈利模式?商机?大数据?移动互联网蛋糕?生活方式改变withApp?
物联网.云服务.大数据.. 淘宝.12306.卡通...一个产品改变一次生活方式. 打车app,无疑是改变生活方式的又一个产品.从打车软件,你能看到什么? 个人认为,打车软件值得各种投资方斥资,最重要 ...