android自定义起止时间的时间刻度尺,Android 自定义View篇(六)实现时钟表盘效果...
前言
Android 自定义 View 是高级进阶不可或缺的内容,日常工作中,经常会遇到产品、UI 设计出花里胡哨的界面。当系统自带的控件不能满足开发需求时,就只能自己动手撸一个效果。
本文就带自定义 View 初学者手动撸一个效果,通过自定义 View 实现钟表功能,每行代码都有注释,保证易懂,看不懂你留言打我!!!
实现效果
1、先看效果图
在这里插入图片描述
2、下载地址
3、步骤分析
实现以上效果,主要分为四个步骤:
绘制外层表盘
绘制刻度线
绘制刻度数字
绘制指针
测量View宽高
代码实现
1、绘制外层表盘
外层表盘就是一个空心圆,只要获取圆的 x、y 轴位置、圆的半径,使用 Canvas.drawCircle()方法即可完成。
/**
* 绘制表盘
*/
private fun drawClock(canvas: Canvas, centerX: Float, centerY: Float) {
// 设置外层圆画笔宽度
mPaint.strokeWidth = mCircleWidth
// 设置画笔颜色
mPaint.color = Color.BLACK
// 设置画笔空心风格
mPaint.style = Paint.Style.STROKE
// 绘制圆方法
canvas.drawCircle(centerX, centerY, radius, mPaint)
}
在这里插入图片描述
2、绘制刻度线
绘制思路分析
看到效果图上密密麻麻刻度线后,先不要着急上手,屡清楚绘制思路。绘制刻度线一定要结合 Canvas 几何变换思路完成,千万不要局限于效果图的表面(如果对 Canvas 相关 API 不熟悉的朋友,建议先了解下)。
假设以 12 点钟为例,那么刻度线就是一条笔直的竖线,调用 Canvas.drawLine()方法完成绘制。
如果每绘制完成一个刻度,把表盘逆时针/顺时针旋转一定角度,将下次需要绘制刻度线的位置旋转到 12 点钟位置,那么每次绘制刻度线的 startX、startY、stopX、stopY 一致(一致仅代表所有长刻度一致,所有短刻度一致)。
观察表盘共有 60 个刻度线(12 长,48 短),那么每次旋转角度degrees=360/60
听完以上分析,是否觉得绘制刻度线很简单,只要在 60 个刻度遍历判断长短,即可轻松出效果。
/**
* 绘制表盘刻度
*/
private fun drawClockScale(canvas: Canvas, centerX: Float, centerY: Float) {
for (index in 1..60) {
// 刻度绘制以12点钟为准,每次将表盘旋转6°,后续绘制都以12点钟为基准绘制
canvas.rotate(6F, centerX, centerY)
// 绘制长刻度线
if (index % 5 == 0) {
// 设置长刻度画笔宽度
mPaint.strokeWidth = 4.0F
// 绘制刻度线
canvas.drawLine(
centerX,
centerY - radius,
centerX,
centerY - radius + scaleMax,
mPaint
)
}
// 绘制短刻度线
else {
// 设置短刻度画笔宽度
mPaint.strokeWidth = 2.0F
canvas.drawLine(
centerX,
centerY - radius,
centerX,
centerY - radius + scaleMin,
mPaint
)
}
}
}
以上代码就完成了绘制刻度线的效果,下面插个题外话,第一次尝试在绘制刻度线的时候,表盘数字一并完成,后来发现数字如下图所示:
// 测量绘制数字
mPaint.strokeWidth = 1.0F
mPaint.style = Paint.Style.FILL
mPaint.getTextBounds((index / 5).toString(), 0, (index / 5).toString().length, mRect)
val width = mRect.width()
canvas.drawText(
(index / 5).toString(),
centerX - mRect.width() / 2,
(centerY - radius + scaleMax + mRect.height() + 8),
mPaint
)
3、绘制数字方案
热心网友指导我绘制数字新方案,真的是高手如云阿。
首先将坐标位置(0,0)设置到圆心位置,这步是在绘制外层圆的时候,已经设置了。这样的好处是后期减少很多计算的步骤,新方案已经在代码中更改!
canvas.translate(centerX, centerY)
主要是通过 canvas 几何变换方式,先将圆点平移到 12 点钟位置,然后逆时针旋转数字对应的角度,然后开始绘制数字文本。这样的话,绘制数字文本就和绘制刻度线可以一并完成,使得代码清晰很多。需要注意的是,记得在使用几何变换前后分别调用canvas.restore()和 canvas.restore()方法。
其中相关坐标计算方式:
1、平移 y 轴距离 = - 半径 + 刻度线长度 + 刻度与文本间距 + 文本高度 / 2
(因为坐标原点在圆心,需要平移到 12 点钟位置,所以半径为负数)
2、旋转角度 = - 6 * 数字大小
3、文本 x 轴距离 = 文本宽度 / 2 ;
4、文本 y 轴距离 = 文本高度 / 2 ;
附上绘制刻度线和文本的完整代码:
/**
* 绘制表盘刻度线和数字文本
*/
private fun drawClockScale(canvas: Canvas) {
for (index in 1..60) {
// 刻度绘制以12点钟为准,每次将表盘旋转6°,后续绘制都以12点钟为基准绘制
canvas.rotate(6F, 0F, 0F)
// 绘制长刻度线
if (index % 5 == 0) {
// 设置长刻度画笔宽度
mPaint.strokeWidth = 4.0F
// 绘制刻度线
canvas.drawLine(0F, -radius, 0F, -radius + scaleMax, mPaint)
/** 绘制文本 **/
canvas.save()
// 设置画笔宽度
mPaint.strokeWidth = 1.0F
// 设置画笔实心风格
mPaint.style = Paint.Style.FILL
mPaint.getTextBounds(
(index / 5).toString(),
0,
(index / 5).toString().length,
mRect
)
canvas.translate(0F, -radius + mNumberSpace + scaleMax + (mRect.height() / 2))
canvas.rotate((index * -6).toFloat())
canvas.drawText(
(index / 5).toString(), -mRect.width() / 2.toFloat(),
mRect.height().toFloat() / 2, mPaint
)
canvas.restore()
}
// 绘制短刻度线
else {
// 设置短刻度画笔宽度
mPaint.strokeWidth = 2.0F
canvas.drawLine(0F, -radius, 0F, -radius + scaleMin, mPaint)
}
}
}
4、绘制指针
指针绘制具体分以下步骤:
首先获取当前时间
根据当前时间计算指针旋转过的角度
利用 Canvas.rotate()旋转画布
使用 Canvas.drawRoundRect()绘制指针矩形
绘制圆点
/**
* 第四步:绘制指针
*/
private fun drawPointer(canvas: Canvas, centerX: Float, centerY: Float) {
// 获取当前时间:时分秒
val calendar = Calendar.getInstance()
val hour = calendar[Calendar.HOUR]
val minute = calendar[Calendar.MINUTE]
val second = calendar[Calendar.SECOND]
// 计算时分秒转过的角度
val angleHour = (hour + minute.toFloat() / 60) * 360 / 12
val angleMinute = (minute + second.toFloat() / 60) * 360 / 60
val angleSecond = second * 360 / 60
// 绘制时针
canvas.save()
// 旋转到时针的角度
canvas.rotate(angleHour, centerX, centerY)
val rectHour = RectF(
centerX - mHourPointWidth / 2,
centerY - radius / 2,
centerX + mHourPointWidth / 2,
centerY + radius / 6
)
// 设置时针画笔属性
mPaint.color = Color.BLUE
mPaint.style = Paint.Style.STROKE
mPaint.strokeWidth = mHourPointWidth
canvas.drawRoundRect(rectHour, mPointRange, mPointRange, mPaint)
canvas.restore()
// 绘制分针
canvas.save()
// 旋转到分针的角度
canvas.rotate(angleMinute, centerX, centerY)
val rectMinute = RectF(
centerX - mMinutePointWidth / 2,
centerY - radius * 3.5f / 5,
centerX + mMinutePointWidth / 2,
centerY + radius / 6
)
// 设置分针画笔属性
mPaint.color = Color.BLACK
mPaint.strokeWidth = mMinutePointWidth
canvas.drawRoundRect(rectMinute, mPointRange, mPointRange, mPaint)
canvas.restore()
// 绘制秒针
canvas.save()
// 旋转到分针的角度
canvas.rotate(angleSecond.toFloat(), centerX, centerY)
val rectSecond = RectF(
centerX - mSecondPointWidth / 2,
centerY - radius + 10,
centerX + mSecondPointWidth / 2,
centerY + radius / 6
)
// 设置秒针画笔属性
mPaint.strokeWidth = mSecondPointWidth
mPaint.color = Color.RED
canvas.drawRoundRect(rectSecond, mPointRange, mPointRange, mPaint)
canvas.restore()
// 绘制原点
mPaint.style = Paint.Style.FILL
canvas.drawCircle(
(centerX).toFloat(),
(centerY).toFloat(), mSecondPointWidth * 4, mPaint
)
}
以上就已经完成表盘指针绘制工作,效果图如下:
在这里插入图片描述
5、onMeasure 测量 View 宽高
MeasureSpecMode 有三个属性:EXACTLY、AT_MOST、UNSPECIFIED
根据 View 在 xml 设置宽高类型不同会触发相应的方法,这里对 onMeasure()测量不做具体讲解,如果对自定义 View 测量宽高 onMeasure()方法不熟悉的,请自己补习。onMeasure()方法也是自定义 View 学习过程中非常重要的一个环节!
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// 根据表盘半径 + 表盘圆环宽度计算View宽度和高度
mWidth = onMeasuredSpec(widthMeasureSpec) + (mCircleWidth * 2).toInt()
mHeight = onMeasuredSpec(heightMeasureSpec) + (mCircleWidth * 2).toInt()
// 计算最终View表盘的半径
radius = (mWidth - mCircleWidth * 2) / 2
// 设置View最终宽高
setMeasuredDimension(mWidth, mHeight)
}
private fun onMeasuredSpec(measureSpec: Int): Int {
// 临时值,用于计算后返回
var specViewSize = 0
val specMode = MeasureSpec.getMode(measureSpec)
val specSize = MeasureSpec.getSize(measureSpec)
when (specMode) {
MeasureSpec.EXACTLY -> {
// 一般为固定尺寸或者match_parent
specViewSize = specSize
}
MeasureSpec.AT_MOST -> {
// 计算半径以宽高最小值为准
specViewSize = min((radius * 2).toInt(), specSize)
}
}
return specViewSize
}
到此为止,如果想让表盘和指针动起来,还需要在 onDraw()方法里调用postInvalidateDelayed(1000)方法,或者启动一个线程,使每秒钟刷新重绘一次布局,这样就可以让指针实时更新时间。最后贴上 onDraw()方法里面的代码:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 设置圆心X轴位置
val centerX: Float = (mWidth / 2).toFloat()
// 设置圆心Y轴位置
val centerY: Float = (mHeight / 2).toFloat()
/** 第一步:绘制最外层的圆 **/
drawClock(canvas, centerX, centerY)
/** 第二步:表盘一共60个刻度,1到12点整数属于长刻度,其余属于短刻度 **/
drawClockScale(canvas, centerX, centerY)
/** 第三步:绘制表盘数字 **/
drawClockNumber(canvas, centerX, centerY)
/** 第四步:绘制指针 **/
drawPointer(canvas, centerX, centerY)
// 刷新表盘指针
postInvalidateDelayed(1000)
}
总结
自定 view 在 Android 开发过程中应用极其广泛,为了更好的掌握,建议从自定义 View 绘制流程、Canvas、Paint、Path、onLayout()、onMeasure()、onDraw() 系统化学习,然后上手多做练习,这样势必会对自定义 View 有很好的提升!
看完文章,是不是觉得这个效果其实也很简单,案例中相关属性可以使用自定义属性,因为练习案例,所以这里在 View 中直接写死了,感兴趣的朋友可以使用自定义属性尝试实现。这个案例基本上将自定义 View 中 Canvas、Paint 常见的 API 方法以及 onMeasure()测量方法都应用到了,算是一个上手练习自定义 View 的好案例,希望看完文章对你学习有所帮助!
前文说过,保证每个自定义 View 初学者都能看懂,因为每行代码都会添加注释,如果没看懂的留言打我!!!
Android 自定义View篇(一)View绘制流程
Android 自定义View篇(二)Canvas详解
Android 自定义View篇(三)Paint详解
Android 自定义View篇(四)自定义属性详解
Android 自定义View篇(五)事件分发机制
android自定义起止时间的时间刻度尺,Android 自定义View篇(六)实现时钟表盘效果...相关推荐
- android是什么牌手机6,好用不贵系列 篇六:我心目中最好的手机:魅蓝note6
好用不贵系列 篇六:我心目中最好的手机:魅蓝note6 2020-03-21 20:47:04 17点赞 16收藏 51评论 最近,我手机屏幕脆了,换了外屏后,指纹识别不灵敏了.于是顺理成章的淘汰给我 ...
- android canvas_Android 自定义View篇(七)实现环形进度条效果
前言 Android 自定义 View 是高级进阶不可或缺的内容,日常工作中,经常会遇到产品.UI 设计出花里胡哨的界面.当系统自带的控件不能满足开发需求时,就只能自己动手撸一个效果. 本文就带自定义 ...
- Android轻松实现日期选择器、生日选择器、自定义起始时间
前言 Android轻松实现日期选择器.生日选择器.自定义起始时间 废话不多说 看下效果 效果图 代码实现 代码实现比较简单 按照步骤 你也可以实现同样的效果 第一步 设置依赖 android 和an ...
- Android 节操视频播放器jiecaovideoplayer自定义播放音频使用:屏蔽全屏按钮,增加倒计时,当前时间/总时间
一.屏蔽全屏按钮 找到JCVideoPlayerStandard.java文件中的代码: private void fixAudio() {if (SrcType.equalsIgnoreCase(& ...
- Android学习最详细的总结学习(花费很多时间结合自己学习Android及求职经历做的总结希望能帮助到大家)
问题咨询及项目源码下载请加群: 群名:IT项目交流群 群号:245022761 1.android事件分发机制,请详细说下整个流程 事件分发(面试).png 2.android view绘制机制和加载 ...
- dede自定义表单增加添加时间怎么弄
我们在用dedecms添加自定义表单时有时想要设置一个用户提交的时间,方便查询,比如我们的客服人员查询昨天晚上下班后有哪些订单是刚生成的,比较好查看,如下图所示.那么,dedecms自定义表单增加添加 ...
- android 闹钟服务,如果闹钟时间已经过去,android可以防止即时触发闹钟服务
如果闹钟时间已经过去,android可以防止即时触发闹钟服务 报警pipe理器的参考说 如果所述的触发时间在过去,则会立即触发警报. 我在申请中遇到了这个问题. 这是我的报警pipe理员代码: Int ...
- Android 查看App冷启动时间/热启动时间/页面打开时间
Android 查看App冷启动时间/热启动时间/页面打开时间 冷启动时间 热启动时间 页面打开时间 通过adb查看 adb shell am start -W packageName/Activit ...
- Android学习之Image操作及时间日期选择器
一.基础学习 1.ImageView是图片容器,就相当于RadioGroup是RadioButton的容器一样,是View的直接子类. 1: <ImageView 2: android:id=& ...
最新文章
- 阿士比亚:搜索团队智能内容生成实践
- LeetCode【位运算】371. 两整数之和
- C++ 11 新特性(十二)函数新特性、内联函数、const详解
- SpringBoot配置RunDashboard
- Tomcat报错 严重: A child container failed during start
- 缓存jQuery对象来提高性能
- 先有本地代码,后有远程仓库
- B 站 CEO 的身份证被上传到 GitHub 了?这个火了
- 使用Java生成PDF文件
- vmrc安装出现:未能安装 HCmon 驱动程序 (Failed to install the HCmon driver)
- L1-059 敲笨钟 (20 分)
- 其他——dhtmlxGantt甘特图API精华总结
- 《决胜B端》读书笔记04:互联网领域常见产品方向、盈利模式、盈利模式对产品方向的诉求
- 最便宜的方式学习阿里云产品之使用竞价式实例ECS
- python怎么退出全屏模式_notepad
- 一度智信:拼多多探路知识普惠
- node.js学习总结:node.js的内置模块,模块化,npm与包 express,前后端身份认证 JWT认证机制
- 微软surface屏幕测试软件,Soomal作品 - Microsoft 微软 Surface Go平板电脑屏幕测评报告 [Soomal]...
- 自己写的ajax通用 脚本
- SQL Server 2008 R2 完全卸载与重新安装
热门文章
- C语言学习教程之详解C语言中的字符串数组
- 惹某人突然不舍de第七周(习题+感悟)
- 关于罗马数字转整数的实现
- 优秀的项目跟踪管理软件有哪些?
- 脉搏波形分析_国家脉搏2020年美国总统大选的推特分析
- 3分钟搞明白信用评分卡模型模型验证
- Mixed Content: The page was loaded over HTTPS,blocked the content must be served over HTTPS.
- 全球重力异常值和磁场异常值提取
- 答题微信小程序实现(4):数据库题库的调用/上一题、下一题/题量length的获取
- PMP/高项 项目管理培训大纲