Android 自定义球型水波纹带圆弧进度效果(实例代码)
需求
如下,实现一个圆形水波纹,带进度,两层水波纹需要渐变显示,且外围有一个圆弧进度。
思路
外围圆弧进度:可以通过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控制属性,这里就不做过多的修改了。
整体实现
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 自定义球型水波纹带圆弧进度效果(实例代码),希望对大家有所帮助
上一篇: vue的常用组件操作方法应用分析
下一篇: Vue2.0 实现单选互斥的方法