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 ...
随机推荐
- 用U盘安装系统2
这种方式USB启动盘制作成功之后是可以往里面存放任何资料的,我喜欢用这一种 首先,在网站上下载一个你想要装的系统 (百度一下优优系统,大地系统,深度技术,MSDN我告诉你,都可以,看你自己喜欢了) 例 ...
- 《疯狂VirtualBox实战讲学录》
<疯狂VirtualBox实战讲学录:小耗子之VirtualBox修炼全程重现>是市面上第一部同时也是唯一一部完整介绍VirtualBox的“中文版全程实战手册”!本书完整记录了Virtu ...
- apache开源项目-- NiFi
Apache NiFi 是一个易于使用.功能强大而且可靠的数据处理和分发系统.Apache NiFi 是为数据流设计.它支持高度可配置的指示图的数据路由.转换和系统中介逻辑. 架构: 集群管理器: 主 ...
- linux 系统获取网络ip, mask, gateway, dns信息小程序
net_util.c #define WIRED_DEV "eth0" #define WIRELESS_DEV ...
- Spring_Springmvc_mybatis一般配置
web.xml配置 <?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi=&quo ...
- LittleTools之网格输出为模型
我经常要在Unity中生成一些网格,但是这些网格需要交给美工修改,所以又要将网格输出为模型.于是就有了下面的代码: using UnityEngine; using UnityEditor; usin ...
- dev/null和dev/zero区别 以及换回设备(loopback device)
转自:http://blog.chinaunix.net/uid-20729677-id-765105.html dev/zero,是一个输入设备,你可你用它来初始化文件. /dev/zero---- ...
- bzoj 3172 [Tjoi2013]单词(fail树,DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3172 [题意] 题目的意思是这样的,给若干个单词,求每个单词在这一堆单词中的出现次数. ...
- spoj 7258 SUBLEX(SAM,名次)
[题目链接] http://www.spoj.com/problems/SUBLEX/en/ [题意] 给定一个字符串,询问次序为k的子串. [思路] SAM,名次 建好SAM后求出每个结点根据tra ...
- NOIP2001 数的划分
题二 数的划分(20分) 问题描述 将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5; 1,5,1; 5,1,1; 问 ...