上效果图: 

以下标记一些绘图的核心点:

① 测量自定义view的大小

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getDimension(DEFAULT_WIDTH, widthMeasureSpec);
    int height = getDimension(DEFAULT_HEIGHT, heightMeasureSpec);
    viewW = width;
    cenP.x = viewW / 2;
    cenP.y = height / 2;
    radius = Math.min(viewW, height) / 2;
    setMeasuredDimension(width, height);
}private int getDimension(int defaultSize, int measureSpec) {int result;
    int measureMode = MeasureSpec.getMode(measureSpec);
    int measureSize = MeasureSpec.getSize(measureSpec);
    if (measureMode == MeasureSpec.EXACTLY) {result = measureSize;
    } else if (measureMode == MeasureSpec.AT_MOST) {result = Math.min(defaultSize, measureSize);
    } else {result = defaultSize;
    }return result;
}

自定义控件的一般测量测量方式, 大家温习一下

②获取系统时间,这里我使用的是Calendar,非常方便,小小的不足是返回时间是24制式

private void getTime() {Calendar calendar = Calendar.getInstance();
    int hour = calendar.get(Calendar.HOUR_OF_DAY);
    int min = calendar.get(Calendar.MINUTE);
    int sec = calendar.get(Calendar.SECOND);
    if (hour > 12) {hour = hour - 12;
    }angelS = 360 * sec / 60f;
    angelM = 360 * min / 60f + sec / 60f;
    angelH = 360 * hour / 12f + 360 * min / (60 * 12f) + 360 * sec / (60 * 60 * 12f);
    angelStartS = angelS;
    angelStartM = angelM;
    angelStartH = angelH;
    dateOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
    dateOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
}

这里获取的系统时间作用:计算进入View时,时针、分针、秒针的角度——也就是一开始的初始角度。【这是最重要的一步】

之后的表针转动问题就相对简单了——获取每帧秒针角度angleS,则每帧分针角度angleM = angleS/60,每帧时针角度angleH = angle/3600。

**这里多说几句: 为了实现指针跟随系统时间,我首先想到的使用Animation,而没有使用线程去一直跑。

因为本自定义控件就是简单实现一个时钟,所有工作都可以交给主线程完成,所以使用Animation更为方便,然后通过设置周期单元duration 和 插值器Interpolator 以及循环属性repeatMode和循环次数数repeatCount,这样一个跟随系统时间,简单方便的逻辑就基本形成。

有了清晰的思路,接下来的工作就按部就班了:

首先,我们来获取绘制表盘背景所需要的Bitmap ,

然后初始化一下绘画的一些相关对象:

private void init() {src = BitmapFactory.decodeResource(getResources(), R.drawable.iron_bg);

    //初始化绘画相关
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    cenP = new PointF();
    path = new Path();
    Animation animation = new Animation() {@Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {super.applyTransformation(interpolatedTime, t);
            float mAngelS = 24 * 60 * 360 * interpolatedTime;
            angelS = angelStartS + mAngelS;
            angelM = angelStartM + mAngelS / 60f;
            angelH = angelStartH + mAngelS / 3600f;
            invalidate();
        }};
    animation.setDuration(24 * 60 * 60 * 1000);//一天24个小时为一个周期
    animation.setRepeatCount(Animation.INFINITE);
    animation.setRepeatMode(Animation.REVERSE);
    animation.setInterpolator(new LinearInterpolator());
    startAnimation(animation);
}

有了这些工作,之后剩下的就是绘制各个图形元素了:

首先绘制背景图,这里因为表盘是圆形的,所以使用到Path,通过画布canvas.clipPath()剪裁已经添加了圆形路径的path,得到我们需要的圆形画布,然后在画布上绘制图片。

【**View 绘画的思路跟ps很相似, 当然了如果你没有使用过ps, 那么更形象的比喻就是,跟裁缝剪裁衣服一样。不同的是,裁缝更多的时候是拼接,而画布则更多的是层叠。】

mPaint.setAntiAlias(true);
path.reset();
path.addCircle(cenP.x, cenP.y, radius, Path.Direction.CW);
canvas.clipPath(path);
canvas.drawBitmap(src, 0, 0, mPaint);
path.close();

绘制好了背景,再来为表盘加一个黑色边框

mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6.18f * 2);
mPaint.setColor(Color.BLACK);
canvas.drawCircle(cenP.x, cenP.y, radius, mPaint);

接下来是什么? 。。。。。。聪明的你或许想到,接下来就是绘制表盘刻度, 然后是表针...done

但是先让我们绘制日期和星期 —— 因为前面说到了, View的绘制是层叠的,而现实中指针肯定实在日期和星期的上面, 所以必须先绘制出较底层的层次:

//日期
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(0);
mPaint.setTextSize(44);
canvas.drawText(String.valueOf(dateOfMonth), cenP.x + radius / 4, cenP.y + 22, mPaint);
//星期
mPaint.setTextSize(44);
String weekDay = null;
switch (dateOfWeek) {case 1:weekDay = "SUN";
        break;
    case 2:weekDay = "MON";
        break;
    case 3:weekDay = "TUE";
        break;
    case 4:weekDay = "WED";
        break;
    case 5:weekDay = "THU";
        break;
    case 6:weekDay = "FRI";
        break;
    case 7:weekDay = "SAT";
        break;
    default:break;
}
assert weekDay != null;
canvas.drawText(weekDay, cenP.x + radius / 2, cenP.y + 22, mPaint);

那么这里,我们就来到了指针的绘制步骤啦:

//绘制时针
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(20f);
float hLen = radius / 2f;
canvas.drawLine(cenP.x, cenP.y, (float) (viewW / 2f + hLen * Math.sin(angelH * Math.PI / 180f)),
        (float) (radius - hLen * Math.cos(angelH * Math.PI / 180f)), mPaint);

//绘制分针
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(12.5f);
float mLen = 2 * radius / 3f;
canvas.drawLine(cenP.x, cenP.y, (float) (viewW / 2f + mLen * Math.sin(angelM * Math.PI / 180f)),
        (float) (radius - mLen * Math.cos(angelM * Math.PI / 180f)), mPaint);

//绘制秒针
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(5f);
float sLen = 5 * radius / 6f;
canvas.drawLine(cenP.x, cenP.y, (float) (viewW / 2f + sLen * Math.sin(angelS * Math.PI / 180f)),
        (float) (radius - sLen * Math.cos(angelS * Math.PI / 180f)), mPaint);

*** 需要简单说一下,,,因为时、分、秒针长度不同, 则我们先设定各个指针的长度,然后指针长度和设定的画笔大小、颜色, 依据各自的角度绘制出一条线,即我们的指针。

***另外,时、分、秒针的绘制顺序依据 (先——时 - 分 - 秒——后),原因同上,就是个层叠问题哈

为了让三个指针看上去更加真实, 而不是简单的将三根线段堆叠在一起,这里还需要在指针轴心点绘制一个圆点,覆盖在最顶层的秒针上。

这样就像我们真实的手表一样啦——三根针一次层叠,然后一根轴心柱插在交点处,如此很好理解啦, 代码如下:

mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(cenP.x, cenP.y, radius / 30, mPaint);

哈哈,快到站咯...........................

忘了点什么?

对, 我们的刻度呢? ———— 为了避免遮盖,这里我们最后绘制刻度。   为了简单,我简单的绘制了1——12个文字,没有真的绘制出刻度(计算是个大而细的工作,各位可以根据自己喜好去慢慢实现, 原理都简单、相似, 但是过程复杂,呵呵( ̄▽ ̄)")

//12个点
float txtSize = 35f;
mPaint.setTextSize(txtSize);
mPaint.setStrokeWidth(3.09f);
float rR = radius - 35f;
for (int i = 0; i < 12; i++) {String txt;
    if (i == 0) {txt = "12";
    } else {txt = String.valueOf(i);
    }canvas.drawText(txt, (float) (viewW / 2f - txtSize / 2f + rR * Math.sin(i * 30 * Math.PI / 180f)), (float) (radius + txtSize / 2f - rR * Math.cos(i * 30 * Math.PI / 180f)), mPaint);

}

-----------------------------------------------------------------------------------------------Moses 分割线---------------------------------------------------------------------------------------------------------------

最后,但绝不是最次要的来了。 ——————如何,让我们的指针跑起来——前面说了,我们使用Animation:

Animation animation = new Animation() {@Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {super.applyTransformation(interpolatedTime, t);
        float mAngelS = 24 * 60 * 360 * interpolatedTime;
        angelS = angelStartS + mAngelS;
        angelM = angelStartM + mAngelS / 60f;
        angelH = angelStartH + mAngelS / 3600f;
        invalidate();
    }
};
animation.setDuration(24 * 60 * 60 * 1000);//一天24个小时为一个周期
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.REVERSE);
animation.setInterpolator(new LinearInterpolator());
startAnimation(animation);

注意一下几点: ①定义一个循环,单元为24小时,也就是一天。————从进入该时钟View开始,各个指针将在24小时后回到进入时的状态(位置、角度)。

②一个循环完成后,Animation重复repeat起来,这样,一个简约而不无乐趣的时钟控件就完成啦。

所有代码在222行左右, 如果出去添加表盘背景图片, 180行足够。

以上!

附上源码地址: http://download.csdn.net/detail/donmoses/9138165点击打开链接

安卓Android自定义控件:200行代码实现一个简约时钟相关推荐

  1. 爬虫python代码-Python爬虫教程:200行代码实现一个滑动验证码

    Python爬虫教程:教你用200行代码实现一个滑动验证码 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还是很清晰的,本文章大 ...

  2. 前端 验证码隐藏怎么实现_Python爬虫教程:200行代码实现一个滑动验证码

    Python爬虫教程:教你用200行代码实现一个滑动验证码 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还是很清晰的,本文章大 ...

  3. python爬虫代码-Python爬虫教程:200行代码实现一个滑动验证码

    Python爬虫教程:教你用200行代码实现一个滑动验证码 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还是很清晰的,本文章大 ...

  4. SpringBoot,用200行代码完成一个一二级分布式缓存

    缓存系统的用来代替直接访问数据库,用来提升系统性能,减小数据库复杂.早期缓存跟系统在一个虚拟机里,这样内存访问,速度最快. 后来应用系统水平扩展,缓存作为一个独立系统存在,如redis,但是每次从缓存 ...

  5. 200行代码实现一个滑动验证码

    作者 | 崔庆才 转载自进击的Coder(ID: FightingCoder) 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还 ...

  6. float js 正则 验证_爬虫篇 | 200 行代码实现一个滑动验证码

    最近整理一个爬虫系列方面的文章,不管大家的基础如何,我从头开始整一个爬虫系列方面的文章,让大家循序渐进的学习爬虫,小白也没有学习障碍. 爬虫篇 | Python使用正则来爬取豆瓣图书数据 爬虫篇 | ...

  7. 200 行代码实现一个滑动验证码

    做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还是很清晰的,本文章大致说明下这些验证码的原理以及带大家实现一个滑动验证码. 我之 ...

  8. 200行代码构建一个区块链

    区块链的基本概念非常简单:一个存储不断增加的有序记录的分布式数据库.然而,当我们谈论区块链时,我们很容易将其与区块链要解决的问题混淆,比如误解为流行的,基于区块链的,像比特币和以太坊一样的项目.术语& ...

  9. 200 行代码实现一个简单的区块链应用1

    区块链的基础概念很简单:一个分布式数据库,存储一个不断加长的 list,list 中包含着许多有序的记录.然而,在通常情况下,当我们谈到区块链的时候也会谈起使用区块链来解决的问题,这两者很容易混淆.像 ...

最新文章

  1. 鸟哥Linux私房菜_基础篇(第二版)_第七章学习笔记
  2. 异常导致循环退出_Java异常有哪些?异常怎么处理?
  3. 北斗导航 | 获取观测卫星的位置信息,并绘制卫星的方位角和仰角得到星空图:GSV语句(附Matlab源代码)
  4. CORS 跨域-同源
  5. ubuntu 15.10下cmake 的安装
  6. css中border制作各种形状
  7. 程序员的第一份实习!附面试初体验
  8. java yml value_Spring Boot:从YAML文件加载@Value
  9. 将数学模型用matlab,matlab课件第3章_控制系统的数学模型及其转换.ppt
  10. STM32寄存器操作端口模式SDA_OUT()/SDA_IN()
  11. 全国省市区(县)级地名xml(一)
  12. esp8266使用安信可IDE编译、烧录
  13. Docker安装filebeat
  14. 背景图页面缩小会变形_HTML中怎么让背景图片跟着浏览器窗口变大变小?
  15. unity项目 别踩白块儿
  16. 压缩包 zip RAR 7z 密码破解常用的几种方法
  17. JAVA incept_(jsp+servlet)ERP管理系统 - WEB源码|JSP源码/Java|源代码 - 源码中国
  18. 【C语言答案】第四次练习---循环进阶
  19. [区块链]区块链技术在商品溯源流通领域的应用
  20. mysql 默认字符集_设定mysql的默认字符集

热门文章

  1. 生产环境安装centos7系统教程(组装服务器)
  2. 基于Vue2实现的仿手机QQapp(支持对话功能,滑动删除....)—— 聊聊开发过程中踩到的一些坑与解决方案,以及个人感悟...
  3. AEB前装搭载率逼近50%,沃尔沃「标配」激光雷达释放信号
  4. MySQL部署及编译安装
  5. 发外链还有用吗?2020外链怎么发才能真正提升网站排名?
  6. 分享几个发外链的地方
  7. 万达金融梦醒:快钱被罚千万,小贷牌照被注销
  8. 计算机组成原理作业五
  9. python读取excel文件
  10. 调试器扩展SOSEX