概述:

很多时候我们想要自己写一些类似时钟、罗盘的控件,却又找不到合适的Demo。我想这时你可能索性就直接上图片了。在Android有Canvas和Paint这么好的画师的情况下,还是选择使用图片,的确是有一些尴尬了。下面我就利用一步一步实现自定义时钟来对这个问题做一个讲解。(注明:本人不太会制作GIF图片,以下图片均不能动态展示,想要查看动态效果,请转到博客末尾处下载源码进行查看)

错误示例:

这里我有一个“错误”的示例。这里的错误其实应该是要打上双引号的,因为它不是真的错误,只是在某些时候,它是不适当的。下面就让我们先来学习一下这个示例,了解一下这个示例中哪些是不适合使用的技术。

效果图展示:

看了上面的两张运行效果图我们可以看到很正常的两张运行图,不过这不是全部。错误信息下面再进行展示和分析。在这里我就来解释一下为什么说这个示例不是全错,只是不恰当的原因。因为如果我们的需求是不用变化的图形,例如一些多边形的展示等,不需要实时去刷新界面,OK,这个示例没有任何问题,而且使用简单。针对这一点,我想也是有必要附上代码来展示一下实现过程。

静态画图代码:

public class CustomCanvasView extends View {

private static final String TAG = CustomCanvasView.class.getName();

private Paint paint;

private int mRadius;

private Canvas mCanvas;

private int mHours;

private int mMinutes;

private int mSeconds;

private Thread mThread;

public CustomCanvasView(Context context, int radius) {

super(context);

paint = new Paint();

paint.setColor(Color.RED);

paint.setStrokeJoin(Paint.Join.ROUND);

paint.setStrokeCap(Paint.Cap.ROUND);

paint.setStrokeWidth(5);

mRadius = radius;

}

// 在这里我们将测试canvas提供的绘制图形方法

@Override

protected void onDraw(Canvas canvas) {

mCanvas = canvas;

drawCompass(mCanvas);

refreshClock();

}

private void refreshClock() {

mThread = new Thread() {

@Override

public void run() {

try {

while (true) {

handler.sendEmptyMessage(0x123);

sleep(1000);

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

};

mThread.start();

}

Handler handler = new Handler() {

public void handleMessage(Message msg) {

Calendar c = Calendar.getInstance();

mHours = c.getTime().getHours();

mMinutes = c.getTime().getMinutes();

mSeconds = c.getTime().getSeconds();

invalidate();

c = null;

};

};

/**

* 绘制罗盘

* 2015-2-3

*/

private void drawCompass(Canvas canvas) {

paint.setAntiAlias(true);

paint.setStyle(Style.STROKE);

canvas.translate(canvas.getWidth() / 2, mRadius + 300); // 平移罗盘

canvas.drawCircle(0, 0, mRadius, paint); // 画圆圈

// 使用path绘制路径文字

canvas.save();

drawLabel(canvas);

canvas.restore();

drawDividing(canvas);

drawMinuteHand(canvas, 0);

canvas = null;

}

/**

* 绘制罗盘内侧的标签文本

* 2015-2-4

*/

private void drawLabel(Canvas canvas) {

canvas.translate(-155, -155);

Path path = new Path();

path.addArc(new RectF(0, 0, mRadius + 100, mRadius + 100), -180, 180);

Paint citePaint = new Paint(paint);

citePaint.setTextSize(30);

citePaint.setStrokeWidth(1);

canvas.drawTextOnPath("http://blog.csdn.net/lemon_tree", path, 35, 0, citePaint);

path = null;

citePaint = null;

canvas = null;

}

/**

* 绘制刻度

* 2015-2-4

*/

private void drawDividing(Canvas canvas) {

Paint divdPaint = new Paint(paint); // 小刻度画笔对象

divdPaint.setStrokeWidth(1);

divdPaint.setTextSize(20);

float y = mRadius;

int count = 60; // 总刻度数

canvas.rotate(35 * 360 / count, 0f, 0f);

for (int i = 0; i < count; i++) {

if (i % 5 == 0) {

canvas.drawLine(0f, y, 0, y + 20f, paint);

canvas.drawText(String.valueOf(i / 5 + 1), -4f, y + 55f, divdPaint);

} else {

canvas.drawLine(0f, y, 0f, y + 15f, divdPaint);

}

canvas.rotate(360 / count, 0f, 0f); // 旋转画纸

}

divdPaint = null;

canvas = null;

}

/**

* 绘制分针

* 2015-2-4

*/

private void drawMinuteHand(Canvas canvas, int second) {

Paint tmpPaint = new Paint(paint);

tmpPaint.setStrokeWidth(2);

tmpPaint.setTextSize(30);

tmpPaint.setColor(Color.GRAY);

tmpPaint.setStrokeWidth(4);

canvas.drawCircle(0, 0, 10, tmpPaint);

tmpPaint.setStyle(Style.FILL);

tmpPaint.setColor(Color.YELLOW);

canvas.drawCircle(0, 0, 5, tmpPaint);

canvas.rotate(mSeconds * 6, 0f, 0f);

canvas.drawLine(0, 20, 0, -135, paint);

tmpPaint = null;

canvas = null;

}

}

错误日志展示及原因分析:

是不是看了上面的两张图感觉没什么大问题,可是如果你下载了我的源码并运行之后,你可能就会发现,在你的指针走了大概20秒的时候,程序就挂了。查看日志就会发现如下错误信息:

是不是有一种有是该死的OOM问题的感觉,说实话我也是这种感觉。这可能是因为invalidate()的时候没有清理回收资源的问题,而且这里的自定义控件是继承View,没有采用双缓冲技术,致使程序崩溃。而此处的资源回收我也做了一些努力,可是问题依旧存在。于是我就开始找寻另一条路径来解决问题——SurfaceView。

----------------------------------------- Split -------------------------------------------

正确示例:

前导知识学习——脏矩形:

所谓脏矩形刷新,意为仅刷新有新变化的部分所在的矩形区域,而其他没用的部分就不去刷新,以此来减少资源浪费。我们可以通过在获取Canvas画布时,为其指派一个参数来声明我们需要画布哪个局部,这样就可以只获得这个部分的控制权.(参考来自:http://www.linuxidc.com/Linux/2012-02/54367.htm)本例中,使用的是全局刷新。

前导知识学习——双缓冲:

关于双缓冲的概念,这里引用一下百度百科的说明(点击进入)。

如果要按照我的理解来通俗地讲一遍的话,我想应该是这个样子的:有一个暗房,里面有一个功能深厚的画师,他负责绘制图画。暗房对外提供了一个小窗口,这个小窗口是用来展示画师画出来的图画。这个暗房里还有一个画师的助理,他负责把画师画出来的图画以一定速度展示在这个小窗口上(这边的一定速度肯定是比画师绘画的速度要慢一些)。

实例示范:

运行效果图展示:

看到以上的运行效果图是不感觉很弦?写出来的时候,我也感觉比用图片实现的要好很多。接下来就来慢慢学习一下实现它的过程吧。

首先要做的事

1.extends SurfaceView

2.implements SurfaceHolder.Callback

3.自定义一个Thread

第二步:逻辑功能实现

基于上一个不恰当的版本,这里对上面的逻辑功能进行一些引用。

绘制秒针:

/**

* 绘制秒针

* 2015-2-4

*/

private void drawSecondHand(Canvas canvas) {

Paint handPaint = new Paint(mPaint);

handPaint.setStrokeWidth(2);

handPaint.setStyle(Style.FILL);

int angle = (mSeconds + 25) * 6; // 计算角度

canvas.rotate(angle, 0f, 0f);

canvas.drawLine(0, 20, 0, -135, mPaint);

}

绘制分针:

/**

* 绘制分针

* 2015-2-4

*/

private void drawMinuteHand(Canvas canvas) {

Paint handPaint = new Paint(mPaint);

handPaint.setStrokeWidth(2);

handPaint.setStyle(Style.FILL);

canvas.save();

int angle = (mMinutes + 25) * 6; // 计算角度

canvas.rotate(angle, 0f, 0f);

canvas.drawLine(0, 20, 0, -110, mPaint);

canvas.restore();

} 从秒针到分针代码明显多了几行,而这多出来的几行代码有什么作用呢?

在绘制分针的时候我们可以看到这样一句:canvas.rotate(angle, 0f, 0f);它的作用是将画布旋转angle度,而如果我们在绘制分针的时候不对画布作一个状态保存,那下次在绘制时针的时候将是旋转之后所做的逻辑,为了避免这些不必要的麻烦,我们需要对其先保存后再复原处理。

绘制时针:

/**

* 绘制时针

* 2015-2-4

*/

private void drawHourHand(Canvas canvas) {

Paint handPaint = new Paint(mPaint);

handPaint.setStyle(Style.FILL);

handPaint.setStrokeWidth(8);

canvas.save();

int angle = (((mHours % 12) * 5 + 25) * 6) + (mMinutes * 6 * 5 / 60); // 计算角度

canvas.rotate(angle, 0f, 0f);

canvas.drawLine(0, 20, 0, -90, handPaint);

canvas.restore();

} 时针的绘制和分针几乎一致,唯一要注意的是绘制时针时角度的计算。如果你这里只按小时数来计算,那它永远都是指向大刻度。永远不会指向两个大刻度之间的部分,为了解决这个问题,我们需要加上分钟数一起计算。即加了n分钟下时针又偏移了多少角度。

自定义Thread

使用SurfaceView需要用到一个锁的机制。也就是说我这边绘图的时候,不允许被打扰,有一个独占的概念。可以通过以下代码实现:

class DrawThread extends Thread {

private SurfaceHolder holder;

public boolean isRun;

public DrawThread(SurfaceHolder holder) {

this.holder = holder;

isRun = true;

}

@Override

public void run() {

while (isRun) {

Canvas canvas = null;

try {

synchronized (holder) {

canvas = holder.lockCanvas(null);

canvas.drawColor(Color.BLACK);

drawClock(canvas);

holder.unlockCanvasAndPost(canvas); // 解锁画布,提交画好的图像

Thread.sleep(1000);

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

} 大家可以看到在我上完锁之后,对画布有一行canvas.drawColor(Color.BLACK);的代码操作。我想你应该是明白为什么的。对!就是清屏!如果没有这一句代码,上一次绘制的图形没有被清除,这让整个界面感觉起来很凌乱。下面就让我们一起来感受一下在没有清屏且只有一根指针的情况下,Canvas动态绘制出来的图形。

大家可以明显看到时钟内侧的那一行Label,白色的部分在一点一点地加深,这就有力地说明了是因为上一次图形的残留导致的。

好了,利用SurfaceView和Canvas对自定义时钟的学习就到这里了,如果你还有一些不太明白的地方,欢迎前往我的上一篇博客《Android自定义控件前导基础知识学习(一)——Canvas》进行学习,或以评论的方式与我进行交流。

s时钟画布 android,Android UI编程进阶——使用SurfaceViewt和Canvas实现动态时钟相关推荐

  1. Android UI编程进阶——使用SurfaceViewt和Canvas实现动态时钟

    概述: 很多时候我们想要自己写一些类似时钟.罗盘的控件,却又找不到合适的Demo.我想这时你可能索性就直接上图片了.在Android有Canvas和Paint这么好的画师的情况下,还是选择使用图片,的 ...

  2. ​第一本 Compose 图书上市,联想大咖教你学会 Android 全新 UI 编程

    朱江 | 现任联想(北京)有限公司 Android 开发工程师,从事 Android 开发工作多年,有丰富的项目经验,负责和参与开发过多款移动应用程序,同时还是多个开源项目的作者.2017 年开始在 ...

  3. 我的新书:《Jetpack Compose:Android全新UI编程》已出版

    上面的图片就是新书的样子,是不是挺好看的,哈哈哈,我个人是这样认为的

  4. Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)【转】

    转自:http://blog.csdn.net/droidphone/article/details/8112948 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 数据结构 ...

  5. Html5基于Canvas画一个动态时钟

    文章目录 前言 一.前期准备 二.绘制刻度 1.流程 2.效果图 三.绘制文字 1.流程 2.效果图 四.绘制指针 1.取得当前时间 2.绘制秒针 3.绘制分针 4.绘制时针 5.效果图 五.绘制圆心 ...

  6. Html5代码实现动态时钟

    以下是一个简单的HTML5动态时钟的示例: <!DOCTYPE html> <html> <head><title>HTML5动态时钟</titl ...

  7. CSS+JS动态时钟(获取当前时间)

    话不多说,先上代码 <!DOCTYPE html> <html><head><meta charset="utf-8"><ti ...

  8. Android UI编程之自定义控件初步(下)——CustomEditText

    概述: 基于对上一篇博客<Android UI编程之自定义控件初步(上)--ImageButton>的学习,我们对自定义控件也有了一个初步的认识.那现在我们可以再试着对EditText进行 ...

  9. Android UI编程之自定义控件初步(上)——ImageButton

    概述: 我想我们在使用一些App的时候,应该不会出现一些"裸控件"的吧.除非是一些系统中的软件,那是为了保持风格的一致性,做出的一些权衡.我这里并非是在指责Android原生的控件 ...

最新文章

  1. poj2420A Star not a Tree?(模拟退火)
  2. Spring Batch事务处理
  3. Go 语言编程 — 程序结构
  4. 【转载】地球物理经典书目——成像方向
  5. 看代码的软件_软件著作权申请中常见的补正问题及解决方式
  6. C++ #define详解
  7. 图片向上滚动字幕代码html,如何通过制作滚动字幕的软件实现这种片尾的向上滚动字幕效果...
  8. Vmware 中Linux中NAT网络异常解决方法
  9. html边框塌陷怎么,你不知道的CSS(边框塌陷)?
  10. Spring Boot : 自定义 Starter
  11. 天津市七下计算机课程,七年级下册信息技术课程教案.doc
  12. html5触摸指定区域,HTML5/CSS3系列教程:HTML5 区域(Sectioning)的重要性
  13. 在C#中获取如PHP函数time()一样的时间戳
  14. Linux系统瓶颈排查
  15. python引用配置文件_python中配置文件的使用方法
  16. Pool tag list
  17. 如何把图片制作做成GIF表情包?分享在线快速制作GIF图片的方法
  18. 吞食天地2忘云殇8.77图文攻略
  19. maven deploy jar包和源码包到私服
  20. html 表单 设计编辑器,可视化页面编辑器的架构设计

热门文章

  1. Maven project deploy to Nexus
  2. express运行原理
  3. C 文件操作库函数总结
  4. OO学习之二——面向对象分析(OOD)的介绍
  5. 验证视图MAC失败 Validation of ViewState MAC Failed
  6. 在vs2005中调用远程WebService(幻想曲)
  7. 使用Javaweb实现在线调查问卷系统
  8. HDFS以IO流的形式上传和下载文件案例
  9. Java知识系列 -- 反射
  10. vijos 1512 SuperBrother打鼹鼠