溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android如何實(shí)現(xiàn)球型水波紋帶圓弧進(jìn)度效果

發(fā)布時(shí)間:2021-06-28 09:19:58 來(lái)源:億速云 閱讀:182 作者:小新 欄目:移動(dòng)開(kāi)發(fā)

這篇文章主要為大家展示了“Android如何實(shí)現(xiàn)球型水波紋帶圓弧進(jìn)度效果”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Android如何實(shí)現(xiàn)球型水波紋帶圓弧進(jìn)度效果”這篇文章吧。

需求

如下,實(shí)現(xiàn)一個(gè)圓形水波紋,帶進(jìn)度,兩層水波紋需要漸變顯示,且外圍有一個(gè)圓弧進(jìn)度。

Android如何實(shí)現(xiàn)球型水波紋帶圓弧進(jìn)度效果

思路

外圍圓弧進(jìn)度:可以通過(guò)canvas.drawArc()實(shí)現(xiàn)。由于圓弧需要實(shí)現(xiàn)漸變,可以通過(guò)給畫(huà)筆設(shè)置shader(SweepGradient)渲染,為了保證圓弧起始的顏色值始終一致,需要?jiǎng)討B(tài)調(diào)整shader的參數(shù)。具體參見(jiàn)

SweepGradient(centerX.toFloat(), centerY.toFloat(), circleColors[0], floatArrayOf(0f, value / 100f))

第四個(gè)參數(shù)需要根據(jù)當(dāng)前進(jìn)度填寫(xiě)對(duì)應(yīng)數(shù)據(jù)比例。不懂的同學(xué)可以自行百度查閱。

水波紋的實(shí)現(xiàn):直接使用貝塞爾曲線Path.quadTo()實(shí)現(xiàn),通過(guò)拉伸水平直線繪制波浪效果??梢酝ㄟ^(guò)控制拉伸點(diǎn)(waveAmplitude)距離水平線的高度,達(dá)到波浪高度的控制。至于波浪的移動(dòng),可以通過(guò)移動(dòng)平移水平線的起始位置來(lái)實(shí)現(xiàn),在使用動(dòng)畫(huà)循環(huán)即可,為了能夠穩(wěn)定的顯示,繪制波浪時(shí)需要嚴(yán)格繪制整數(shù)倍周期的波浪。

園形的實(shí)現(xiàn):繪制一個(gè)完整的圓形,然后通過(guò)Path.op()合并裁剪水波紋path。注意點(diǎn)就是Android6有個(gè)坑,使用該方法會(huì)有明顯的抖動(dòng),為了解決該問(wèn)題,我的做法是多畫(huà)一層圓弧以掩蓋此抖動(dòng)。

生命周期的控制:為了減少某些時(shí)刻CPU的損耗,通過(guò)控制變量自定義lifeDelegate(基于kotlin的代理模式實(shí)現(xiàn))來(lái)控制動(dòng)畫(huà)的開(kāi)始暫停。由于筆者使用的框架基于MVVM,所以代碼就沒(méi)有使用attrs控制屬性,這里就不做過(guò)多的修改了。

整體實(shí)現(xiàn)

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坐標(biāo)
 private var centerX = 0
 private var centerY = 0
 private var outerRadius = 0//外圈圓環(huán)的半徑
 private var innerRadius = 250f//內(nèi)部圓圈的半徑
 private var radiusDist = 50f//內(nèi)外圓圈的半徑差距
 private var fWaveShader: LinearGradient? = null
 private var sWaveShader: LinearGradient? = null
 private var wavePath = Path()
 private var waveCirclePath = Path()
 private val waveNum = 2
 //波浪的漸變顏色數(shù)組
 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"))
  )
 }
 //外圍圓環(huán)的漸變色
 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
 //外圍圓圈的畫(huà)筆
 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度開(kāi)啟漸變,由于線條頭部有個(gè)小圓圈會(huì)導(dǎo)致顯示差異,因此從88度開(kāi)始繪制
 private val sweepMatrix by lazy {
  val matrix = Matrix()
  matrix.setRotate(88f, centerX.toFloat(), centerY.toFloat())
  matrix
 }
 //進(jìn)度 0-100
 var percent = 0
  set(value) {
   field = value
   waveWaterLevelRatio = value / 100f
   //y = -4 * x2 + 4x拋物線計(jì)算振幅,水波紋振幅規(guī)律更加真實(shí)
   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
 //外圍圓圈的畫(huà)筆大小
 private var outerStrokeWidth = 10f
 private var fAnimatedValue = 0f
 private var sAnimatedValue = 0f
 //動(dòng)畫(huà)
 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()
  }
 }
 //設(shè)置中間進(jìn)度文本的字體大小
 fun setPercentSize(size: Float) {
  percentSize = size
  invalidate()
 }
 //設(shè)置中間提示文本的字體大小
 fun setGreedSize(size: Float) {
  greedSize = size
  invalidate()
 }
 //設(shè)置文本顏色
 fun setTextColor(color: Int) {
  textColor = color
  textPaint.color = textColor
  invalidate()
 }
 //設(shè)置外圍圓圈的寬度
 fun setOuterStrokeWidth(width: Float) {
  outerStrokeWidth = width
  outerCirclePaint.strokeWidth = outerStrokeWidth
  outerNormalCirclePaint.strokeWidth = outerStrokeWidth
  invalidate()
 }
 //設(shè)置內(nèi)圓半徑
 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()
 }
 //當(dāng)前窗口銷(xiāo)毀時(shí),回收動(dòng)畫(huà)資源
 override fun onDetachedFromWindow() {
  onDestroy()
  super.onDetachedFromWindow()
 }
 override fun onDraw(canvas: Canvas) {
  drawCircle(canvas)
  drawWave(canvas)
  drawText(canvas)
 }
 private fun drawWave(canvas: Canvas) {
  //波浪當(dāng)前高度
  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設(shè)置Path.op存在明顯抖動(dòng),因此多畫(huà)一圈圓環(huán)
  val ringWidth = outerRadius - outerStrokeWidth - innerRadius
  ringPaint.strokeWidth = ringWidth / 2
  canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), innerRadius + ringWidth / 4, ringPaint)
 }
 private fun drawText(canvas: Canvas) {
  //繪制進(jìn)度文字
  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) {
  //繪制外圍進(jìn)度圓圈
  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如何實(shí)現(xiàn)球型水波紋帶圓弧進(jìn)度效果”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI