欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 自定义球型水波纹带圆弧进度效果(实例代码)

程序员文章站 2022-05-08 13:15:07
需求 如下,实现一个圆形水波纹,带进度,两层水波纹需要渐变显示,且外围有一个圆弧进度。 思路 外围圆弧进度:可以通过canvas.drawarc()实现。由于圆弧需要实现渐变...

需求

如下,实现一个圆形水波纹,带进度,两层水波纹需要渐变显示,且外围有一个圆弧进度。

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 自定义球型水波纹带圆弧进度效果(实例代码),希望对大家有所帮助