Android 自定义 View 之圆形进度条总结

最近撸了一个圆形进度条的开源项目,算是第一次完完整整的使用自定义 View 。在此对项目开发思路做个小结,欢迎大家 Star 和 Fork

该项目总共实现了三种圆形进度条效果

  1. CircleProgress:圆形进度条,可以实现仿 QQ 健康计步器的效果,支持配置进度条背景色、宽度、起始角度,支持进度条渐变
  2. DialProgress:类似 CircleProgress,但是支持刻度
  3. WaveProgress:实现了水波纹效果的圆形进度条,不支持渐变和起始角度配置,如需此功能可参考 CircleProgress 自行实现。

先上效果图,有图才好说。
CircleProgress 效果图
a

DialProgress 和 WaveProgress 效果图
b

恩,那么接下来,就来讲讲怎么实现以上自定义进度条的效果。

圆形进度条

圆形进度条是第一个实现的进度条效果,用了我大半天的时间,实现起来并不复杂。

其思路主要可以分为以下几步:

  1. View 的测量
  2. 计算绘制 View 所需参数
  3. 圆弧的绘制及渐变的实现
  4. 文字的绘制
  5. 动画效果的实现

首先,我们要测量出所绘制 View 的大小,即重写 onMeasure() 方法,代码如下:

由于其他两个进度条类都需要实现 View 的测量,这里对代码进行了封装:

关于 View 测量可以看下这篇博客 Android 自定义View 中的onMeasure的用法

接下来,在 onSizeChanged() 中计算绘制圆及文字所需的参数,考虑到屏幕旋转的情况,故未直接在 onMeasure() 方法中直接计算。这里以下面草图来讲解绘制计算过程中的注意事项,图丑,勿怪~
WechatIMG2

图中,外面蓝色矩形为 View,里面黑色矩形为圆的外接矩形,蓝色矩形和黑色矩形中间空白的地方为 View 的内边距(padding)。两个蓝色的圆其实是一个圆,代表圆的粗细,这是因为 Android 在绘制圆或者圆弧的时候是圆的边宽的中心与外接矩形相交,所以在计算的时候要考虑到内边距(padding) 和 圆与外接矩形的相交。

默认不考虑圆弧的宽度,绘制出来的效果如下:
device-2017-03-02-071101

关于 Android 中文字绘制可以参考以下两篇文章:
1. Android 自定义View学习(三)——Paint 绘制文字属性
2. measureText() vs .getTextBounds()

以上,已经基本完成了 View 绘制所需全部参数的计算。接下来就是绘制圆弧及文字了。

绘制圆弧需要用到 Canvas 的

为了方便计算,绘制圆弧的时候使用了 Canvas 的 rotate() 方法,对坐标系进行了旋转

恩,圆环已经绘制完成,那么接下来就是实现圆环的渐变,这里使用 SweepGradient 类。SweepGradient 可以实现从中心放射性渐变的效果,如下图:
1344993412_1866

SweepGradient 类有两个构造方法,

这里我们选择第一个构造方法。由于设置渐变需要每次都创建一个新的 SweepGradient 对象,所以最好不要放到 onDraw 方法中去更新,最好在初始化的时候就设置好,避免频繁创建导致内存抖动。

这里还有一个值得注意的地方,草图如下
WechatIMG3
假设,渐变颜色如下:

因为 SweepGradient 渐变是 360 度的,所以如果你绘制的圆弧只有 270度,则蓝色部分(图中黑色阴影部分)的渐变就会不可见。

接下来,就是文字的绘制了。文字绘制在上述提到的文章中已经进行了详细的讲解,这里就不再赘述。代码如下:

最后,我们来实现进度条的动画效果。这里我们使用 Android 的属性动画来实现进度更新。

这里有两个注意点:
1. 不要在 ValueAnimator.AnimatorUpdateListener 中输出 Log,特别是动画调用频繁的情况下,因为输出 Log 频繁会生成大量 String 对象造成内存抖动,当然也可以使用 StringBuilder 来优化。
2. 关于 invalidate()postInvalidate() 两者最本质的前者只能在 UI 线程中使用,而后者可以在非 UI 线程中使用,其实 postInvalidate() 内部也是使用 Handler 实现的。

关于 Android 属性动画可以参考:
1. Android 属性动画(Property Animation) 完全解析 (上)
2. Android 属性动画(Property Animation) 完全解析 (下)

补充:同一个属性如何支持颜色和颜色数组

考虑到圆弧设置单色和渐变的区别,即单色只需要提供一种色值,而渐变至少需要提供两种色值。可以有以下几种解决方案:

  1. 定义两个属性,渐变的优先级高于单色的。
  2. 定义一个 format 为 string 属性,以 #FFFFFF|#000000 形式提供色值
  3. 定义一个 format 为 color|reference 的属性,其中 reference 属性指代渐变色的数组。

这里选用第三种方案,实现如下:

代码中读取 xml 中配置:

带刻度进度条

前面,详细讲了 CircleProgress 的绘制思路,接下来讲 DialProgress。

实话说,DialProgress 与 CircleProgress 的实现极其相似,因为两者之间其实就差了一个刻度,但考虑到扩展以及类职责的单一,所以将两者分开。

这里主要讲一下刻度的绘制。刻度绘制主要使用 Canvas 类的 save()rotate()restore() 方法,当然你也可以使用 translate() 方法对坐标系进行平移,方便计算。

关于 Canvas 的画布操作可以参考这篇文章:安卓自定义View进阶-Canvas之画布操作

水波纹效果的进度条

水波纹效果的进度条实现需要用到贝塞尔曲线,主要难点在于 绘制区域的计算波浪效果 的实现,其余的逻辑跟上述两种进度条相似。

这里使用了 Path 类,该类在 Android 2D 绘图中是非常重要的,Path 不仅能够绘制简单图形,也可以绘制这些比较复杂的图形。也可以对多个路径进行布尔操作,类似设置 Paint 的 setXfermode() ,具体使用可以参考这篇博客:安卓自定义View进阶-Path基本操作。这里就不再赘述,有机会自己也会对 Android 自定义 View 的知识进行总结,不过,感觉应该了了无期。

继续上示意图,请叫我灵魂画手~
WechatIMG1

图中黑色的圆为我们要绘制的进度条圆,黑色的曲线为初始状态的的波浪,该波浪使用贝塞尔曲线绘制,其中奇数的点为贝塞尔曲线的起始点,偶数的点为贝塞尔曲线的控制点。例如:1——>2——>3就为一条贝塞尔曲线,1 是起点,2 是控制点,3 是终点。从图中可以看到波浪在园内圆外各一个(1—>5 和 5->9),通过对波浪在 x 轴上做平移,即图中蓝色实线,来实现波浪的动态效果,所以一个波浪的完整动画效果需要有两个波浪来实现。同理,通过控制 y 轴的偏移量,即图中蓝色虚线,可以实现波浪随进度的上涨下降。

贝塞尔曲线上起始点和控制点的计算如下:

以上,我们已经获取到绘制贝塞尔曲线所需的路径点。接下来,我们就需要来计算出绘制区域,即使用 Path 类。

WechatIMG1

紫色区域为贝塞尔曲线需要绘制的整体区域。

WechatIMG1
红色区域为上图紫色区域与圆的交集,也就是波浪要显示的区域

代码如下:

以上,就实现了水波动态的效果,当然,你也可以通过配置,来设定水波是否随进度上涨下降。为了实现更好的效果,可以设置一个浅色的水波并支持设置水波的走向(R2L 和 L2R),通过设置浅色波浪和深色波浪动画的时间,从而实现长江后浪推前浪的效果,恩,效果很自然的~自己脑补从右至左波浪的实现和贝塞尔点的计算。

对获取坐标点的代码进行优化:

至此,自定义圆形进度条相关的思路已全部讲述完成。代码已全部上传至 Git ,欢迎大家 Star 和 Fork,传送门:CircleProgress

如有不清楚或者错误的地方,欢迎指出~

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

1 3 收藏 1 评论

关于作者:踏歌行

希望有一天我能够很坦然地说:\"让我来告诉你,在我眼中,这是一个怎样的世界。\" 个人主页 · 我的文章 · 22 ·   

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部