1. 程式人生 > 程式設計 >Android 自定義球型水波紋帶圓弧進度效果(例項程式碼)

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,waveColors[1],Shader.TileMode.CLAMP
     )
     SweepGradient(
       centerX.toFloat(),value / 100f)
     )
    }
    in 47..54 -> {
     fWaveShader = LinearGradient(
       0f,waveColors[2],waveColors[3],circleColors[1],value / 100f)
     )
    }
    else -> {
     fWaveShader = LinearGradient(
       0f,waveColors[4],waveColors[5],circleColors[2],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(),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(),innerRadius + ringWidth / 4,ringPaint)
 }
 private fun drawText(canvas: Canvas) {
  //繪製進度文字
  textPaint.isFakeBoldText = true
  textPaint.textSize = percentSize
  canvas.drawText(
    percent.toString(),centerY.toFloat() + textPaint.textSize / 2,textPaint
  )
  textPaint.isFakeBoldText = false
  textPaint.textSize = greedSize
  canvas.drawText(
    greedTip,centerY.toFloat() - textPaint.textSize * 2,textPaint
  )
 }
 private fun drawCircle(canvas: Canvas) {
  //繪製外圍進度圓圈
  canvas.drawArc(outerCircleRectf,360f,false,outerNormalCirclePaint)
  canvas.drawArc(outerCircleRectf,90f,percent * 3.6f,outerCirclePaint)
  canvas.drawCircle(
    centerX.toFloat(),bgCirclePaint
  )
 }
}

總結

以上所述是小編給大家介紹的Android 自定義球型水波紋帶圓弧進度效果(例項程式碼),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!
如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!