需求
如下,实现一个圆形水波纹,带进度,两层水波纹需要渐变显示,且外围有一个圆弧进度。
思路
外围圆弧进度:可以通过canvas.drawArc()
实现。由于圆弧需要实现渐变,可以通过给画笔设置shader(SweepGradient)
渲染,为了保证圆弧起始的颜色值始终一致,需要动态调整shader的参数。具体参见
SweepGradient(centerX.toFloat(), centerY.toFloat(), circleColors[0], floatArrayOf(0f, value / 100f))
第四个参数需要根据当前进度填写对应数据比例。不懂的同学可以自行百度查阅。
水波纹的实现:直接使用贝塞尔曲线Path.quadTo()实现,通过拉伸水平直线绘制波浪效果。可以通过控制拉伸点(waveAmplitude)距离水平线的高度,达到波浪高度的控制。至于波浪的移动,可以通过移动平移水平线的起始位置来实现,在使用动画循环即可,为了能够稳定的显示,绘制波浪时需要严格绘制整数倍周期的波浪。
园形的实现:绘制一个完整的圆形,然后通过Path.op()合并裁剪水波纹path。注意点就是Android6有个坑,使用该方法会有明显的抖动,为了解决该问题,我的做法是多画一层圆弧以掩盖此抖动。
生命周期的控制:为了减少某些时刻CPU的损耗,通过控制变量自定义lifeDelegate(基于kotlin的代理模式实现)来控制动画的开始暂停。由于笔者使用的框架基于MVVM,所以代码就没有使用attrs控制属性,这里就不做过多的修改了。
整体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
|
class WaveView(context: Context, attributeSet: AttributeSet? = null ) : View(context, attributeSet) { companion object { const val RESUME = 0x1 const val STOP = 0x2 const val DESTROY = 0x3 } private var mWidth = 0 //控件整体宽度 private var mHeight = 0 //控件整体高度 //控件中心位置,x,y坐标 private var centerX = 0 private var centerY = 0 private var outerRadius = 0 //外圈圆环的半径 private var innerRadius = 250f //内部圆圈的半径 private var radiusDist = 50f //内外圆圈的半径差距 private var fWaveShader: LinearGradient? = null private var sWaveShader: LinearGradient? = null private var wavePath = Path() private var waveCirclePath = Path() private val waveNum = 2 //波浪的渐变颜色数组 private val waveColors by lazy { arrayListOf( //深红色 intArrayOf(Color.parseColor( "#E8E6421A" ), Color.parseColor( "#E2E96827" )), intArrayOf(Color.parseColor( "#E8E6421A" ), Color.parseColor( "#E2F19A7F" )), //橙色 intArrayOf(Color.parseColor( "#E8FDA085" ), Color.parseColor( "#E2F6D365" )), intArrayOf(Color.parseColor( "#E8FDA085" ), Color.parseColor( "#E2F5E198" )), //绿色 intArrayOf(Color.parseColor( "#E8009EFD" ), Color.parseColor( "#E22AF598" )), intArrayOf(Color.parseColor( "#E8009EFD" ), Color.parseColor( "#E28EF0C6" )) ) } //外围圆环的渐变色 private val circleColors by lazy { arrayListOf( //深红色 intArrayOf(Color.parseColor( "#FFF83600" ), Color.parseColor( "#FFF9D423" )), //橙色 intArrayOf(Color.parseColor( "#FFFDA085" ), Color.parseColor( "#FFF6D365" )), //绿色 intArrayOf(Color.parseColor( "#FF2AF598" ), Color.parseColor( "#FF009EFD" )) ) } private val wavePaint by lazy { val paint = Paint() paint.isAntiAlias = true paint.strokeWidth = 1f paint } //波浪高度比例 private var waveWaterLevelRatio = 0f //波浪的振幅 private var waveAmplitude = 0f //波浪最大振幅高度 private var maxWaveAmplitude = 0f //外围圆圈的画笔 private val outerCirclePaint by lazy { val paint = Paint() paint.strokeWidth = 20f paint.strokeCap = Paint.Cap.ROUND paint.style = Paint.Style.STROKE paint.isAntiAlias = true paint } private val outerNormalCirclePaint by lazy { val paint = Paint() paint.strokeWidth = 20f paint.color = Color.parseColor( "#FFF2F3F3" ) paint.style = Paint.Style.STROKE paint.isAntiAlias = true paint } private val bgCirclePaint by lazy { val paint = Paint() paint.color = Color.parseColor( "#FFF6FAFF" ) paint.style = Paint.Style.FILL paint.isAntiAlias = true paint } private val textPaint by lazy { val paint = Paint() paint.style = Paint.Style.FILL paint.textAlign = Paint.Align.CENTER paint.isFakeBoldText = true paint.isAntiAlias = true paint } private val ringPaint by lazy { val paint = Paint() paint.style = Paint.Style.STROKE paint.color = Color.WHITE paint.isAntiAlias = true paint } //外围圆圈所在的矩形 private val outerCircleRectf by lazy { val rectF = RectF() rectF.set( centerX - outerRadius + outerCirclePaint.strokeWidth, centerY - outerRadius + outerCirclePaint.strokeWidth, centerX + outerRadius - outerCirclePaint.strokeWidth, centerY + outerRadius - outerCirclePaint.strokeWidth ) rectF } //外围圆圈的颜色渐变器矩阵,用于从90度开启渐变,由于线条头部有个小圆圈会导致显示差异,因此从88度开始绘制 private val sweepMatrix by lazy { val matrix = Matrix() matrix.setRotate(88f, centerX.toFloat(), centerY.toFloat()) matrix } //进度 0-100 var percent = 0 set(value) { field = value waveWaterLevelRatio = value / 100f //y = -4 * x2 + 4x抛物线计算振幅,水波纹振幅规律更加真实 waveAmplitude = (- 4 * (waveWaterLevelRatio * waveWaterLevelRatio) + 4 * waveWaterLevelRatio) * maxWaveAmplitude // waveAmplitude = if (value < 50) 2f * waveWaterLevelRatio * maxWaveAmplitude else (-2 * waveWaterLevelRatio + 2) * maxWaveAmplitude val shader = when (value) { in 0 .. 46 -> { fWaveShader = LinearGradient( 0f, mHeight.toFloat(), 0f, mHeight * ( 1 - waveWaterLevelRatio), waveColors[ 0 ], null , Shader.TileMode.CLAMP ) sWaveShader = LinearGradient( 0f, mHeight.toFloat(), 0f, mHeight * ( 1 - waveWaterLevelRatio), waveColors[ 1 ], null , Shader.TileMode.CLAMP ) SweepGradient( centerX.toFloat(), centerY.toFloat(), circleColors[ 0 ], floatArrayOf(0f, value / 100f) ) } in 47 .. 54 -> { fWaveShader = LinearGradient( 0f, mHeight.toFloat(), 0f, mHeight * ( 1 - waveWaterLevelRatio), waveColors[ 2 ], null , Shader.TileMode.CLAMP ) sWaveShader = LinearGradient( 0f, mHeight.toFloat(), 0f, mHeight * ( 1 - waveWaterLevelRatio), waveColors[ 3 ], null , Shader.TileMode.CLAMP ) SweepGradient( centerX.toFloat(), centerY.toFloat(), circleColors[ 1 ], floatArrayOf(0f, value / 100f) ) } else -> { fWaveShader = LinearGradient( 0f, mHeight.toFloat(), 0f, mHeight * ( 1 - waveWaterLevelRatio), waveColors[ 4 ], null , Shader.TileMode.CLAMP ) sWaveShader = LinearGradient( 0f, mHeight.toFloat(), 0f, mHeight * ( 1 - waveWaterLevelRatio), waveColors[ 5 ], null , Shader.TileMode.CLAMP ) SweepGradient( centerX.toFloat(), centerY.toFloat(), circleColors[ 2 ], floatArrayOf(0f, value / 100f) ) } } shader.setLocalMatrix(sweepMatrix) outerCirclePaint.shader = shader invalidate() } private val greedTip = "Greed Index" //文本的字体大小 private var percentSize = 80f private var greedSize = 30f private var textColor = Color.BLACK //外围圆圈的画笔大小 private var outerStrokeWidth = 10f private var fAnimatedValue = 0f private var sAnimatedValue = 0f //动画 private val fValueAnimator by lazy { val valueAnimator = ValueAnimator() valueAnimator.duration = 1500 valueAnimator.repeatCount = ValueAnimator.INFINITE valueAnimator.interpolator = LinearInterpolator() valueAnimator.setFloatValues(0f, waveWidth) valueAnimator.addUpdateListener { animation -> fAnimatedValue = animation.animatedValue as Float invalidate() } valueAnimator } private val sValueAnimator by lazy { val valueAnimator = ValueAnimator() valueAnimator.duration = 2000 valueAnimator.repeatCount = ValueAnimator.INFINITE valueAnimator.interpolator = LinearInterpolator() valueAnimator.setFloatValues(0f, waveWidth) valueAnimator.addUpdateListener { animation -> sAnimatedValue = animation.animatedValue as Float invalidate() } valueAnimator } //一小段完整波浪的宽度 private var waveWidth = 0f var lifeDelegate by Delegates.observable( 0 ) { _, old, new -> when ( new ) { RESUME -> onResume() STOP -> onPause() DESTROY -> onDestroy() } } //设置中间进度文本的字体大小 fun setPercentSize(size: Float) { percentSize = size invalidate() } //设置中间提示文本的字体大小 fun setGreedSize(size: Float) { greedSize = size invalidate() } //设置文本颜色 fun setTextColor(color: Int) { textColor = color textPaint.color = textColor invalidate() } //设置外围圆圈的宽度 fun setOuterStrokeWidth(width: Float) { outerStrokeWidth = width outerCirclePaint.strokeWidth = outerStrokeWidth outerNormalCirclePaint.strokeWidth = outerStrokeWidth invalidate() } //设置内圆半径 fun setInnerRadius(radius: Float) { innerRadius = radius invalidate() } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super .onSizeChanged(w, h, oldw, oldh) mWidth = width - paddingStart - paddingEnd mHeight = height - paddingTop - paddingBottom centerX = mWidth / 2 centerY = mHeight / 2 outerRadius = mWidth.coerceAtMost(mHeight) / 2 radiusDist = outerRadius - innerRadius waveWidth = mWidth * 1 .8f maxWaveAmplitude = mHeight * 0 .15f } private fun onResume() { if (fValueAnimator.isStarted) { animatorResume() } else { fValueAnimator.start() sValueAnimator.start() } } private fun animatorResume() { if (fValueAnimator.isPaused || !fValueAnimator.isRunning) { fValueAnimator.resume() } if (sValueAnimator.isPaused || !sValueAnimator.isRunning) { sValueAnimator.resume() } } private fun onPause() { if (fValueAnimator.isRunning) { fValueAnimator.pause() } if (sValueAnimator.isRunning) { sValueAnimator.pause() } } private fun onDestroy() { fValueAnimator.cancel() sValueAnimator.cancel() } //当前窗口销毁时,回收动画资源 override fun onDetachedFromWindow() { onDestroy() super .onDetachedFromWindow() } override fun onDraw(canvas: Canvas) { drawCircle(canvas) drawWave(canvas) drawText(canvas) } private fun drawWave(canvas: Canvas) { //波浪当前高度 val level = ( 1 - waveWaterLevelRatio) * innerRadius * 2 + radiusDist //绘制所有波浪 for (num in 0 until waveNum) { //重置path wavePath.reset() waveCirclePath.reset() var startX = if (num == 0 ) { //第一条波浪的起始位置 wavePath.moveTo(-waveWidth + fAnimatedValue, level) -waveWidth + fAnimatedValue } else { //第二条波浪的起始位置 wavePath.moveTo(-waveWidth + sAnimatedValue, level) -waveWidth + sAnimatedValue } while (startX < mWidth + waveWidth) { wavePath.quadTo( startX + waveWidth / 4 , level + waveAmplitude, startX + waveWidth / 2 , level ) wavePath.quadTo( startX + waveWidth / 4 * 3 , level - waveAmplitude, startX + waveWidth, level ) startX += waveWidth } wavePath.lineTo(startX, mHeight.toFloat()) wavePath.lineTo(0f, mHeight.toFloat()) wavePath.close() waveCirclePath.addCircle( centerX.toFloat(), centerY.toFloat(), innerRadius, Path.Direction.CCW ) waveCirclePath.op(wavePath, Path.Op.INTERSECT) //绘制波浪渐变色 wavePaint.shader = if (num == 0 ) { sWaveShader } else { fWaveShader } canvas.drawPath(waveCirclePath, wavePaint) } //Fixme android6设置Path.op存在明显抖动,因此多画一圈圆环 val ringWidth = outerRadius - outerStrokeWidth - innerRadius ringPaint.strokeWidth = ringWidth / 2 canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), innerRadius + ringWidth / 4 , ringPaint) } private fun drawText(canvas: Canvas) { //绘制进度文字 textPaint.isFakeBoldText = true textPaint.textSize = percentSize canvas.drawText( percent.toString(), centerX.toFloat(), centerY.toFloat() + textPaint.textSize / 2 , textPaint ) textPaint.isFakeBoldText = false textPaint.textSize = greedSize canvas.drawText( greedTip, centerX.toFloat(), centerY.toFloat() - textPaint.textSize * 2 , textPaint ) } private fun drawCircle(canvas: Canvas) { //绘制外围进度圆圈 canvas.drawArc(outerCircleRectf, 0f, 360f, false , outerNormalCirclePaint) canvas.drawArc(outerCircleRectf, 90f, percent * 3 .6f, false , outerCirclePaint) canvas.drawCircle( centerX.toFloat(), centerY.toFloat(), innerRadius, bgCirclePaint ) } } |
总结
以上所述是小编给大家介绍的Android 自定义球型水波纹带圆弧进度效果(实例代码),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
原文链接:https://blog.csdn.net/sinat_34430383/article/details/103326970