效果图:

前言:

自定义view,是开发者必备的技能之一,也是找工作时面试官必问的题目。
有文章把自定义控件归纳为三种:
一、自绘控件,即继承View,在onDraw()内使用canvas绘制;
二、组合控件,即把常用的控件组合在一起,变成新的控件;
三、继承控件,即继承一个常用的View,修改、增加某个方法等。

组合控件最常用,自绘控件最体现水平。网上很多入门教程也很详细,本篇也会通过实例细讲绘制过程。总结下来就是更多的:“计算”(计算位置、计算距离等等),所以打开AndroidStudio的同时,也请准备好计算器。

正文

新建StopwatchView 继承View ,除了构造方法外,有两个方法必须得重写:测量尺寸onMeasure(xxx)和绘制图形onDraw(xxx)

public class StopwatchView extends View {public StopwatchView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);}
}
一、onMeasure方法:

系统在绘制图形前,会先测量图形尺寸等相关参数,然后根据尺寸进行绘制。
在Demo中,我们的秒表始终保持圆形,但View的宽高设定可以有三种情况:match_parent、wrap_content、定值,所以我们重写onMeasure()来适配这三种情况

 @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//重新定义尺寸,保证为正方形int width = measuredDimension(widthMeasureSpec);int height = measuredDimension(heightMeasureSpec);mLen = Math.min(width, height);//小三角形指针端点到外圆之间的距离,用于计算三角形坐标[这里取整体宽度的1/16]mTriangleLen = (float) mLen / 16.0f;//提交设置 新的值setMeasuredDimension(mLen, mLen);}//适配不同尺寸private int measuredDimension(int measureSpec) {int defaultSize = 800; //默认大小int mode = MeasureSpec.getMode(measureSpec); //宽高度设定方式int size = MeasureSpec.getSize(measureSpec); //宽高度测量大小switch (mode) {case MeasureSpec.EXACTLY: //尺寸指定return size;case MeasureSpec.AT_MOST: //match_parentreturn size;case MeasureSpec.UNSPECIFIED: //wrap_contentreturn defaultSize;default:return defaultSize;}}

说明:1、mLen 是最终外围宽高度。内部其他各元素的宽高、大小等都要以此为基准。简单来说,就是其他各元素都要按照mLen的值进行比例分配,不能设定死。否则可能出现不同尺寸下,内部元素比例不协调的情况
2、MeasureSpec 看起来比较陌生,其实内部只有三个常量、三个方法,如上面的代码所写,重写目的一是保证宽、高相同,二是在wrap_content时给一个默认值

二、StopwatchView构造方法:

在写onDraw()前,先提一下画笔。因为本例是一个动画效果,需要不停的重复执行ondraw(),所以一些不变的对象,如画笔等应该放在构造方法里。分析全局,需要四个画笔:三角形画笔指针(mTrianglePaint)、mLinePaint(mLinePaint)、文字画笔(mTextPaint)、内部圆形画笔(mInnerCirclePaint)

    public StopwatchView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);//三角形指针画笔mTrianglePaint = new Paint();mTrianglePaint.setColor(Color.WHITE);mTrianglePaint.setAntiAlias(true); //抗锯齿//刻度线的画笔mLinePaint = new Paint();mLinePaint.setAntiAlias(true);mLinePaint.setStrokeWidth(2); //设线宽//文字画笔mTextPaint = new Paint();mTextPaint.setTextAlign(Paint.Align.CENTER); //文字居中mTextPaint.setColor(Color.WHITE);mTextPaint.setAntiAlias(true);mTextPaint.setStrokeWidth(2);//内部圆形画笔mInnerCirclePaint = new Paint();mInnerCirclePaint.setColor(Color.WHITE);mInnerCirclePaint.setStyle(Paint.Style.STROKE); //无填充mInnerCirclePaint.setAntiAlias(true);}
三、onDraw方法:

本例主要的变量为秒表计时的毫秒值mMilliseconds
再根据mMilliseconds值计算出外圆三角形指针的角度outerAngle内部小圆的角度innerAngle,其他图形的绘制是根据这三个参数来进行;
另一个需要强调的是,参考小米秒钟,共设定240条刻度线,并预先设定好每个角度的值:

 float eachLineAngle = 360f / 240f; //两个刻度线之间的角度1.5° 共240条线 240间隔
1、calculateValue() 计算相关值
    //计算相关值【根据当前毫秒值,计算外指针角度和内圆指针角度】private void calculateValue() {//显示文字int hours = mMilliseconds / (1000 * 60 * 60);int minutes = (mMilliseconds % (1000 * 60 * 60)) / (1000 * 60);int seconds = (mMilliseconds - hours * (1000 * 60 * 60) - minutes * (1000 * 60)) / 1000;int milliSec = mMilliseconds % 1000 / 100;if (hours == 0) {mShowContent = toDoubleDigit(minutes) + ":" + toDoubleDigit(seconds) + "." + milliSec;} else {mShowContent = toDoubleDigit(hours) + ":" + toDoubleDigit(minutes) + ":" + toDoubleDigit(seconds) + "." + milliSec;}//外角度outerAngle = 360 * (mMilliseconds % 60000) / 60000;//内角度innerAngle = 360 * (mMilliseconds % 1000) / 1000;}
2、drawTriangle(Canvas canvas) 根据角度绘制三角形
    //根据角度绘制三角形private void drawTriangle(Canvas canvas) {canvas.save();//确定坐标canvas.translate(mLen / 2, mLen / 2);canvas.rotate(outerAngle);//画三角形Path p = new Path();//指针点p.moveTo(0, mLen / 2 - mTriangleLen);//左右侧点p.lineTo(0.5f * mTriangleLen, mLen / 2 - 0.134f * mTriangleLen);p.lineTo(-0.5f * mTriangleLen, mLen / 2 - 0.134f * mTriangleLen);p.close();canvas.drawPath(p, mTrianglePaint);canvas.restore();}

说明:mTriangleLen是之前计算的指针顶点到外边缘的距离。因为没有三角形的api,所以根据路径来绘制。其中:0.5f * mTriangleLen 和 mLen / 2 - 0.134f * mTriangleLen 分别表示以三角形指针另两点的x和y的距离[0.5=sin30°,0.134=(1-cos30°)]

3、drawLine(Canvas canvas) 绘制外部刻度线
 //绘制外部刻度线private void drawLine(Canvas canvas) {canvas.save();canvas.translate(mLen / 2, mLen / 2);int totalLines = (int) (360f / eachLineAngle); //240条线int lastLine = (int) (outerAngle / eachLineAngle);  //最亮的线条int firstLine = lastLine - ((int) (90 / eachLineAngle)); //最暗的一条boolean negativeFlag = false; //负数标志【即表示跨过了0起始坐标】if (firstLine < 0) {negativeFlag = true;firstLine = totalLines - Math.abs(firstLine);}int count = 0;for (int i = 0; i < totalLines; i++) {canvas.rotate(eachLineAngle);int color = 0;if (!negativeFlag) {//没有跨过起始点标志if (i >= firstLine && i <= lastLine && count < (totalLines / 4)) {count++;color = Color.argb(255 - ((totalLines / 4 - count) * 3), 255, 255, 255);} else {color = Color.argb(255 - (int) (360f * 3 / (eachLineAngle * 4)), 255, 255, 255);}} else {//跨过起始点if (i >= 0 && i < lastLine) {if (count == 0) {count = totalLines / 4 - lastLine;} else {count++;}color = Color.argb(255 - ((totalLines / 4 - count) * 3), 255, 255, 255);} else if (mMilliseconds!=0&&i < totalLines && i >= firstLine) {  //mMilliseconds!=0 条件限制,目的是初始化时 都是灰色线条Log.i("TAG6", "firstLine" + firstLine + " lastLine" + lastLine);count++;color = Color.argb(255 - ((totalLines / 4 - (i - firstLine)) * 3), 255, 255, 255);} else {color = Color.argb(255 - (int) (360f * 3 / (eachLineAngle * 4)), 255, 255, 255);}}mLinePaint.setColor(color);//mTriangleLen/5距离 目的是为了三角形到线条之间保留的距离canvas.drawLine(0, (float) (mLen / 2 - (mTriangleLen+mTriangleLen/5)), 0, (float) (mLen / 2 - (2 * mTriangleLen+mTriangleLen/5)), mLinePaint);}canvas.restore();}

说明:绘制线条,先要计算总的线条数,然后for循环,循环中每次旋转eachLineAngle角度。同时要根据当前角度来设定画笔的颜色来达到渐变效果。因为有跨过0°和未跨过0°的情况,所以代码中分别对此做了处理。当然也可能有其它更好的计算方法。其中的有判断 mMilliseconds!=0情况,表示初始情况或重置情况下,颜色不做改变

4、drawText(Canvas canvas) 绘制文字
//绘制文字private void drawText(Canvas canvas) {canvas.save();canvas.translate(mLen / 2, mLen / 2);mTextPaint.setTextSize(mLen / 10);canvas.drawText(mShowContent, 0, 0, mTextPaint);canvas.restore();}
5、drawSecondHand(Canvas canvas) 根据角度绘制内部秒针
    //根据角度绘制内部秒针private void drawSecondHand(Canvas canvas) {canvas.save();canvas.translate(mLen / 2, (float) mLen * 3 / 4.0f - mLen / 16);canvas.drawCircle(0, 0, mLen / 12, mInnerCirclePaint);canvas.drawCircle(0, 0, mLen / 80, mInnerCirclePaint);canvas.rotate(innerAngle);canvas.drawLine(0, mLen / 80, 0, mLen / 14, mInnerCirclePaint);canvas.restore();}
四、增加对外交互的方法
    //开始public void start() {if (mTimer == null) {mTimer = new Timer();mTimer.schedule(new TimerTask() {@Overridepublic void run() {if (!isPause) {![这里写图片描述](https://img-blog.csdn.net/20180415133612672?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Fyc29uNjYzMzAw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)mMilliseconds += 50;//工作线程中用postInvalidate(); UI线程用invalidate()postInvalidate();}}}, 50, 50);} else {resume();}}//暂停public void pause() {isPause = true;}//继续private void resume() {isPause = false;}//重置public void reset() {if (mTimer != null) {mTimer.cancel();mTimer = null;}isPause = false;mMilliseconds = 0;invalidate();}//记录public int record() {return mMilliseconds;}

源码传送门

自定义View-仿小米秒钟相关推荐

  1. Android仿支付宝UI功能开发,Android 自定义view仿支付宝咻一咻功能

    支付宝上有一个咻一咻的功能,就是点击图片后四周有水波纹的这种效果,今天也写一个类似的功能. 效果如下所示: 思路: 就是几个圆的半径不断在变大,这个可以使用动画缩放实现,还有透明动画 还有就是这是好几 ...

  2. 自定义View | 仿QQ运动步数进度效果

    项目GitHub地址 思路 固定不动的蓝色大圆弧 动画变动的红色小圆弧 中间的步数文字显示 相关的自定义属性 比如固定不动的大圆弧, 我们不能写死他的蓝色颜色属性, 要提供一个颜色的自定义属性给用户自 ...

  3. android 高仿 探探卡片滑动,Android自定义View仿探探卡片滑动效果

    Android自定义View仿探探卡片滑动这种效果网上有很多人已经讲解了实现思路,大多都用的是RecyclerView来实现的,但是我们今天来换一种实现思路,只用一个自定义的ViewGroup来搞定这 ...

  4. android wear支付宝6,Android自定义View仿支付宝输入六位密码功能

    跟选择银行卡界面类似,也是用一个PopupWindow,不过输入密码界面是一个自定义view,当输入六位密码完成后用回调在Activity中获取到输入的密码并以Toast显示密码.效果图如下: 自定义 ...

  5. android的动态tab,Android自定义view仿QQ的Tab按钮动画效果(示例代码)

    话不多说 先上效果图 实现其实很简单,先用两张图 一张是背景的图,一张是笑脸的图片,笑脸的图片是白色,可能看不出来.实现思路:主要是再触摸view的时候同时移动这两个图片,但是移动的距离不一样,造成的 ...

  6. 自定义view 仿qq步数 半圆弧

    先看效果图: 自定义属性 <declare-styleable name="QQSteps"><attr name="roundWidth" ...

  7. android 日历仿IOS,基于Android week view仿小米和iphone日历效果

    前言 最近由于项目需求,要做一个仿小米日历的功能,下面显示一天的日程,header以周为单位进行滑动,github上找了很久也没有找到合适的,但找到一相近的开源项目Android-week-view, ...

  8. kotlin实现的简单个人账户管理APP(三) 自定义View仿支付宝的密码输入框/密码相关逻辑

    转载请注明出处:http://blog.csdn.net/a512337862/article/details/78874322 前言 1.本篇博客相关的项目介绍请参考基于kotlin实现的简单个人账 ...

  9. 自定义view仿写今日头条点赞动画

    前言 平时喜欢看今日头条,上面的财经.科技和NBA栏目都很喜欢,无意中发现他的点赞动画还不错,一下子就吸引到了我.遂即想要不自己实现一下. 最终效果对比如下: 头条: 仿写效果: 一.导读 学习的过程 ...

  10. 自定义View 仿QQ运动步数进度效果

    1. 概述   我记得QQ之前是有一个,运动步数的进度效果,今天打开QQ一看发现没有了.具体效果我也不清楚了,我就按照自己大概的印象写一下,这一期我们主要是熟悉Paint画笔的使用:    2. 效果 ...

最新文章

  1. C#中的Dictionary字典类介绍
  2. PHP文件包含漏洞原理分析和利用方法
  3. UA PHYS515 电磁理论I 麦克斯韦方程组基础4 介质中的麦克斯韦方程
  4. 云栖大会抢先看,提前探秘云栖数字谷
  5. 使用log4Net 输出日志到mongodb
  6. 【渝粤教育】电大中专电子商务网站建设与维护 (11)作业 题库
  7. 【Spark】Spark 报错 error writing stream metadata exitcode=1073741515
  8. url解码java_JAVA对URL的解码【转】
  9. python findall_Python 正则表达式:findall
  10. java fakepath_IE浏览器上传文件时本地路径变成”C:\fakepath\”的问题
  11. 如何查看微信小程序的源码存放路径
  12. python read_csv chunk_Python chunk读取超大文件
  13. camera-radar fusinon paper 速递
  14. Altair的离散元分析工具EDEM软件的2022.2版本下载与安装配置教程
  15. golang panic和recover
  16. 设备管理器的蓝牙设备卸载了,找不到蓝牙
  17. ubuntu20.04安装opencv4.7
  18. 【题解】「JSOI2012」玄武密码(AC自动机)
  19. leetcode【135】Candy【c++版,双数组,单数组,坡峰坡谷】
  20. 有源电场与无源磁场与麦克斯韦方程

热门文章

  1. 北京联发科嵌入式软件工程师笔试题目解析
  2. Python 在程序中定义函数fun(s)
  3. hbuilder常用
  4. 关于JSCH使用自义定连接池说明
  5. 视觉SLAM十四讲-高翔 第8讲 视觉里程计2
  6. 帝国理工计算机软件工程,帝国理工学院计算机软件工程理学硕士
  7. 以B2C物流的前世今生,看未来电商物流的决胜之道
  8. matlab 读取 通达信,Matlab 自动导入通达信板块文件
  9. Android小钢琴案例
  10. 一周技术学习笔记(第68期)-像练习硬笔书法那样写代码