效果图:

实现步骤:

  • 绘制表盘[刻度,数字]

  • 绘制指针

  • 让指针走起来~

具体如下:

绘制表盘:

首先需要计算出刻度的起点和终点坐标值,这里我们通过构建两个半径不同的同心圆,大圆半径减小圆半径,就可以得到一条刻度,只用改变角度,就可以获取所有刻度:

    /*** 通过改变角度值,获取不同角度方向的外圆一点到圆心连线过内圆一点的路径坐标集合* @param x0 圆心x* @param y0 圆心y* @param outRadius 外圆半径* @param innerRadius 内圆半径* @param angle 角度* @return 返回*/private float[] getDialPaths(int x0,int y0,int outRadius,int innerRadius,int angle){float[] paths = new float[4];paths[0]  = (float) (x0 + outRadius * Math.cos(angle * Math.PI / 180));paths[1]  = (float) (y0 + outRadius * Math.sin(angle * Math.PI / 180));paths[2]  = (float) (x0 + innerRadius * Math.cos(angle * Math.PI / 180));paths[3]  = (float) (y0 + innerRadius * Math.sin(angle * Math.PI / 180));return paths;}

秒针刻度间隔360/60 = 6 度,循环绘制60次,每一次角度加6,就可以了,绘制代码如下:

        for (int i = 0; i < 60 ; i++) {if (i % 5 == 0){//获取刻度路径float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 5 / 6, -i * 6);canvas.drawLines(dialKdPaths,paintKd30);float[] dialPathsStr = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 3 / 4, -i * 6);canvas.drawText(strKedu[i/5],dialPathsStr[2] - 16,dialPathsStr[3] + 14,paintKd30Text);continue;}float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 7 / 8, -i * 6);canvas.drawLines(dialKdPaths,paintKdSecond);}
绘制指针和旋转指针

这里的重点在于对指针旋转的理解:

通过上图可以看到,我们通过旋转画布,然后绘制指针,最后恢复画布,从而改变了指针的指向,具体操作过程是:

  1. 保存已经绘制画面

  1. 以一定角度旋转画布

  1. 绘制指针

  1. 恢复画布角度

代码如下:以时针绘制为例

        //时针绘制canvas.save(); //保存之前内容canvas.rotate(angleHour,halfMinLength,halfMinLength); //旋转的是画布,从而得到指针旋转的效果canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength*3/4,paintHour);canvas.restore(); //恢复
让时间走起来

通过实时的计算时针,分针,秒针的角度,然后通知重新绘制画面,我们就看到时间在走动。

/*** 更新时分秒针的角度,开始绘制*/public void startRun(){new Thread(new Runnable() {@Overridepublic void run() {while (drawable){try {Thread.sleep(1000); // 睡1supdataAngleSecond(); //更新秒针角度updataAngleMinute(); //更新分针角度updataAngleHour(); //更新时针角度postInvalidate(); //重新绘制} catch (InterruptedException e) {e.printStackTrace();}}}}).start();}

完整代码如下:

public class DialView extends View {private boolean drawable = true; //是否可以绘制private int halfMinLength; //最小宽/高的一半长度private Paint paintKd30; //时针刻度线画笔private Paint paintKd30Text; // 时针数字画笔private Paint paintKdSecond; //秒针刻度线画笔private Paint paintHour;  //时针画笔private Paint paintCircleBar;//指针圆心画笔private Paint paintMinute; //分针画笔private Paint paintSecond; //秒针画笔private float angleHour; //时针旋转角度private float angleMinute; //分针旋转角度private float angleSecond; //秒针旋转角度private int cuurSecond; //当前秒private int cuurMinute; //当前分private int cuurHour; //当前时private Calendar mCalendar;private boolean isMorning = true; //上午/下午private String[] strKedu = {"3","2","1","12","11","10","9","8","7","6","5","4"};public DialView(Context context) {this(context,null);}public DialView(Context context, AttributeSet attrs) {this(context, attrs,-1);}public DialView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initPaint(); //初始化画笔initTime(); //初始化时间}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);halfMinLength = Math.min(width,height) / 2;System.out.println(halfMinLength);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//表盘刻度绘制for (int i = 0; i < 60 ; i++) {if (i % 5 == 0){//获取刻度路径float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 5 / 6, -i * 6);canvas.drawLines(dialKdPaths,paintKd30);float[] dialPathsStr = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 3 / 4, -i * 6);canvas.drawText(strKedu[i/5],dialPathsStr[2] - 16,dialPathsStr[3] + 14,paintKd30Text);continue;}float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 7 / 8, -i * 6);canvas.drawLines(dialKdPaths,paintKdSecond);}//指针绘制//时针绘制canvas.save(); //保存之前内容canvas.rotate(angleHour,halfMinLength,halfMinLength); //旋转的是画布,从而得到指针旋转的效果canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength*3/4,paintHour);canvas.restore(); //恢复//绘制分针canvas.save();canvas.rotate(angleMinute,halfMinLength,halfMinLength); //旋转的是画布,从而得到指针旋转的效果canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength/2,paintMinute);paintCircleBar.setColor(Color.rgb(75,75,75));paintCircleBar.setShadowLayer(4,4,8,Color.argb(70,40,40,40));canvas.drawCircle(halfMinLength,halfMinLength,24,paintCircleBar);canvas.restore();//绘制秒针canvas.save();canvas.rotate(angleSecond,halfMinLength,halfMinLength); //旋转的是画布,从而得到指针旋转的效果canvas.drawLine(halfMinLength,halfMinLength + 40,halfMinLength,halfMinLength / 4 - 20,paintSecond);paintCircleBar.setColor(Color.rgb(178,34,34));paintCircleBar.setShadowLayer(4,4,8,Color.argb(50,80,0,0));canvas.drawCircle(halfMinLength,halfMinLength,12,paintCircleBar);canvas.restore();}/*** 初始化时,分,秒*/private void initTime() {mCalendar = Calendar.getInstance();cuurHour = mCalendar.get(Calendar.HOUR_OF_DAY);cuurMinute = mCalendar.get(Calendar.MINUTE);cuurSecond = mCalendar.get(Calendar.SECOND);if (cuurHour >= 12){cuurHour = cuurHour - 12;isMorning = false;}else{isMorning = true;}angleSecond = cuurSecond * 6f;angleMinute = cuurMinute * 6f;angleHour = cuurHour * 6f * 5f;}/*** 更新时分秒针的角度,开始绘制*/public void startRun(){new Thread(new Runnable() {@Overridepublic void run() {while (drawable){try {Thread.sleep(1000); // 睡1supdataAngleSecond(); //更新秒针角度updataAngleMinute(); //更新分针角度updataAngleHour(); //更新时针角度postInvalidate(); //重新绘制} catch (InterruptedException e) {e.printStackTrace();}}}}).start();}private void updataAngleHour() {//更新时针角度angleHour = angleHour + (30f/3600);if (angleHour >= 360){angleHour = 0;cuurHour = 0;}}private void updataAngleMinute() {//更新分针角度angleMinute = angleMinute + 0.1f;if (angleMinute >= 360){angleMinute = 0;cuurMinute = 0;cuurHour += 1;}}private void updataAngleSecond() {//更新秒针角度angleSecond = angleSecond + 6;cuurSecond += 1;if (angleSecond >= 360){angleSecond = 0;cuurSecond = 0;cuurMinute += 1;//一分钟同步一次本地时间mCalendar = Calendar.getInstance();cuurHour = mCalendar.get(Calendar.HOUR_OF_DAY);cuurMinute = mCalendar.get(Calendar.MINUTE);cuurSecond = mCalendar.get(Calendar.SECOND);if (cuurHour >= 12){cuurHour = cuurHour - 12;isMorning = false;}else{isMorning = true;}angleSecond = cuurSecond * 6f;angleMinute = cuurMinute * 6f;angleHour = cuurHour * 6f * 5f;}}/*** 停止绘制*/public void stopDrawing(){drawable = false;}/*** 通过改变角度值,获取不同角度方向的外圆一点到圆心连线过内圆一点的路径坐标集合* @param x0 圆心x* @param y0 圆心y* @param outRadius 外圆半径* @param innerRadius 内圆半径* @param angle 角度* @return 返回*/private float[] getDialPaths(int x0,int y0,int outRadius,int innerRadius,int angle){float[] paths = new float[4];paths[0]  = (float) (x0 + outRadius * Math.cos(angle * Math.PI / 180));paths[1]  = (float) (y0 + outRadius * Math.sin(angle * Math.PI / 180));paths[2]  = (float) (x0 + innerRadius * Math.cos(angle * Math.PI / 180));paths[3]  = (float) (y0 + innerRadius * Math.sin(angle * Math.PI / 180));return paths;}/*** 初始化画笔参数*/private void initPaint() {paintKd30 = new Paint();paintKd30.setStrokeWidth(8);paintKd30.setColor(Color.rgb(75,75,75));paintKd30.setAntiAlias(true);paintKd30.setDither(true);paintKd30.setStrokeCap(Paint.Cap.ROUND);paintKd30Text = new Paint();paintKd30Text.setTextAlign(Paint.Align.LEFT); //左对齐paintKd30Text.setStrokeWidth(6); //设置宽度paintKd30Text.setTextSize(40); //文字大小paintKd30Text.setTypeface(Typeface.DEFAULT_BOLD); //加粗paintKd30Text.setColor(Color.rgb(75,75,75)); //画笔颜色paintKd30Text.setAntiAlias(true); //抗锯齿paintKd30Text.setDither(true); //抖动paintKd30Text.setStrokeCap(Paint.Cap.ROUND); //笔尖圆角paintKd30Text.setShadowLayer(4,2,4,Color.argb(60,90,90,90)); //阴影paintKdSecond = new Paint();paintKdSecond.setStrokeWidth(6);paintKdSecond.setColor(Color.rgb(75,75,75));paintKdSecond.setAntiAlias(true);paintKdSecond.setDither(true);paintKdSecond.setStrokeCap(Paint.Cap.ROUND);paintKdSecond.setShadowLayer(4,5,10,Color.argb(50,80,80,80));paintHour = new Paint();paintHour.setStrokeWidth(30);paintHour.setColor(Color.rgb(75,75,75));paintHour.setAntiAlias(true);paintHour.setDither(true);paintHour.setStrokeCap(Paint.Cap.ROUND);paintHour.setShadowLayer(4,5,10,Color.argb(50,80,80,80));paintCircleBar = new Paint();paintCircleBar.setStrokeWidth(6);
//        paintCircleBar.setColor(Color.rgb(178,34,34));paintCircleBar.setAntiAlias(true);paintCircleBar.setDither(true);paintCircleBar.setStrokeCap(Paint.Cap.ROUND);
//        paintCircleBar.setShadowLayer(4,5,10,Color.argb(100,80,80,80));paintMinute = new Paint();paintMinute.setStrokeWidth(30);paintMinute.setColor(Color.rgb(75,75,75));paintMinute.setAntiAlias(true);paintMinute.setDither(true);paintMinute.setStrokeCap(Paint.Cap.ROUND);paintMinute.setShadowLayer(4,5,10,Color.rgb(80,80,80));paintSecond = new Paint();paintSecond.setStrokeWidth(6);paintSecond.setColor(Color.rgb(180,30,30));paintSecond.setAntiAlias(true);paintSecond.setDither(true);paintSecond.setStrokeCap(Paint.Cap.ROUND);paintSecond.setShadowLayer(4,2,10,Color.argb(100,90,90,90));}
}

Android自定义实现绘制时钟表盘相关推荐

  1. android自定义弧度按钮,Android 自定义View 绘制六边形设置按钮

    今天逛酷安的时候,发现酷安的设置按钮(截图的右上角),是一个六边形 + 中心圆的图标,所以又是一个自定义View练习对象了.画圆很简单,知道半径即可,而重点就在画出六边形. 酷安截图.png 最终效果 ...

  2. Android自定义View绘制闹钟

    Android自定义View绘制闹钟 本文简单实现了一个闹钟,扩展View,Canvas绘制 效果如下: 代码如下: package com.gaofeng.mobile.clock_demo;imp ...

  3. Android 自定义View绘制电池图标

    /*** @anthor GrainRain* @funcation 自定义View绘制电池* @date 2019/8/27*/ public class DrawBatteryView exten ...

  4. Android自定义View绘制流程

    Android视图层次结构简介 在介绍View绘制流程之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系. 我们平 ...

  5. android 自定义View绘制电池电量(电池内带数字显示)

    最新公司需要一个电池内带数字的显示电池电量需求,百度了一下.参考下面这篇文章写的Android自定义View之电池电量显示. 增加了里面电池电量数字显示,还有就是一个屏幕适配.不管屏幕分辨率基本都能适 ...

  6. Android 自定义View 绘制五角星

    背景 之前写过的App里有评分的功能,而显示评分一般使用系统的RatingBar再加自定义,一切都很完美,但是产品提了一个需求,例如4.6.4.7.5.8分,不要显示为4个星星加一个半星(4.5分), ...

  7. android画a4矩形,Android自定义View绘制原理:画多大?画在哪?画什么?(三)

    View绘制就好比画画,抛开Android概念,如果要画一张图,首先会想到哪几个基本问题: 画多大? 画在哪? 怎么画? Android绘制系统也是按照这个思路对View进行绘制,上面这些问题的答案分 ...

  8. Android自定义View绘制闪闪发光的文字

    如何实现类似网页效果中闪闪发光的文字,通过自定义View可以实现这一炫酷效果 1.自定义View public class FlickTextView extends TextView {privat ...

  9. Android 自定义View绘制的基本开发流程 Android自定义View(二)

    1 View绘制的过程 View的测量--onMeasure() View的位置确定--onLayout() View的绘制--onDraw() 2 View的测量--onMeasure() Andr ...

最新文章

  1. 呵呵,又在这里开了个博客,现在已经有好几个了
  2. TF之LiR:基于tensorflow实现机器学习之线性回归算法
  3. insert 和 insertSelective的区别
  4. mysql en dump_mysqldump 命令总结
  5. 网站设计中程序员和美工的配合问题
  6. 解读革命性容器集群CCE Turbo:计算、网络、调度全方位加速
  7. 洪水填充算法_洪水填充(Flood fill)算法
  8. ssm+共享图书管理系统 毕业设计-附源码151121
  9. Winxp系统文件夹完全解析
  10. iQOO Neo6 SE什么时候发布 iQOO Neo6 SE配置如何
  11. scrapy 架构文档
  12. virtual box linux 安装增强功能,在linux系统中安装virtualbox增强功能(增强包)的详细步骤...
  13. 联想小新锁屏壁纸怎么换_如何设置联想小新电脑锁屏时间
  14. JDBC execute常用方法
  15. 鼠标放在可以点击的文字上,变成小手样式
  16. 2021年工业软件行业研究报告
  17. CSS文字超出部分用省略号代替,js鼠标悬停时显示全部文本
  18. 把list集合转换为JSON
  19. 究竟什么是图数据库,它有哪些应用场景?
  20. 自己动手写数据库系统:容灾恢复原理和容灾恢复日志的设计

热门文章

  1. 1202:Pell数列
  2. 四柱排盘系统--记录软件开发过程
  3. bagging与随机森林(python实现)
  4. WPF 修改屏幕亮度
  5. LENOVO Thinkpad notebook验机
  6. xmind基础教程-视图
  7. 基于GMap.Net的天地图使用
  8. 90天入门UE引擎开发--学习日记(30/100)
  9. JavaScript - 使用IE打开本地可执行文件
  10. vue3 + vite 使用csss全局变量