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 自定义球型水波纹带圆弧进度效果(实例代码),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • Android自定义View实现圆弧进度效果

    前言:Android开发中,自定义View实现自己想要的效果已成为一项必备的技能,当然自定义View也是Android开发中比较难的部分,涉及到的知识有Canvas(画布),Paint(画笔)等,自定义控件分为三种:一是直接继承自View,完全的自定义:二是在原有控件的基础上进行改造,达到自己想要的效果:还有一种就是自定义组合控件,将已有的控件根据自己的需要进行组合实现的效果.本人对自定义View也是一知半解,简单记录下自己学习自定义View(继承自View)的过程,方便日后翻阅. 技术实现 1

  • Android仿水波纹流量球进度条控制器

    仿水波纹流球进度条控制器,Android实现高端大气的主流特效,供大家参考,具体内容如下 效果图: CircleView 这里主要是实现中心圆以及水波特效 package com.lgl.circleview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.gra

  • Android自定义view实现水波纹进度球效果

    今天我们要实现的这个view没有太多交互性的view,所以就继承view. 自定义view的套路,套路很深 1.获取我们自定义属性attrs(可省略) 2.重写onMeasure方法,计算控件的宽和高 3.重写onDraw方法,绘制我们的控件 这么看来,自定义view的套路很清晰嘛. 我们看下今天的效果图,其中一个是放慢的效果(时间调的长) 我们按照套路来. 一.自定义属性 <declare-styleable name="WaveProgressView"> <at

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

    需求 如下,实现一个圆形水波纹,带进度,两层水波纹需要渐变显示,且外围有一个圆弧进度. 思路 外围圆弧进度:可以通过canvas.drawArc()实现.由于圆弧需要实现渐变,可以通过给画笔设置shader(SweepGradient)渲染,为了保证圆弧起始的颜色值始终一致,需要动态调整shader的参数.具体参见 SweepGradient(centerX.toFloat(), centerY.toFloat(), circleColors[0], floatArrayOf(0f, value

  • Android自定义View 实现水波纹动画引导效果

    一.实现效果图 二.实现代码 1.自定义view package com.czhappy.showintroduce.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Pat

  • Android 自定义view实现水波纹动画效果

    在实际的开发中,很多时候还会遇到相对比较复杂的需求,比如产品妹纸或UI妹纸在哪看了个让人兴奋的效果,兴致高昂的来找你,看了之后目的很明确,当然就是希望你能给她: 在这样的关键时候,身子板就一定得硬了,可千万别说不行,爷们儿怎么能说不行呢: 好了,为了让大家都能给妹纸们想要的,后面会逐渐分享一些比较比较不错的效果,目的只有一个,通过自定义view实现我们所能实现的动效: 今天主要分享水波纹效果: 1.标准正余弦水波纹: 2.非标准圆形液柱水波纹: 虽说都是水波纹,但两者在实现上差异是比较大的,一个

  • Android自定义View实现水波纹扩散效果

    目录 1.创建RippleView.class, 继承与View 1.1特殊属性解释 1.2新建attrs.xml文件(res/values) 1.3初始化画笔 2.开始绘制onDraw() 效果:水波纹扩散 场景:雷达.按钮点击效果.搜索等 实现:先上效果图,之前记得支付宝有一个咻一咻,当时就是水波纹效果,实现起来一共两步,第一画内圆,第二画多个外圆,不同时创建有间隔创建然后缓慢增大外圆半径,到达最远距离时移除掉,扩散时把透明度从255-1不断赋值即可.复杂在第二步,开工. 开工 1.创建Ri

  • Android自定义dialog 自下往上弹出的实例代码

    具体代码如下所示: package com.example.idmin.myapplication.wiget; import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.wid

  • Android自定义View实现水波纹引导动画

    一.实现效果图 关于贝塞尔曲线 二.实现代码 1.自定义view package com.czhappy.showintroduce.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.grap

  • Android自定义View实现水波纹效果

    介绍:水波纹散开效果的控件在 App 里面还是比较常见的,例如 网易云音乐歌曲识别,附近搜索场景. 看下实现的效果: 实现思路: 先将最大圆半径与最小圆半径间距分成几等份,从内到外,Paint 透明度依次递减,绘制出同心圆,然后不断的改变这些同心圆的半径大小,延迟一定时间重绘,便达到了想外散开的动画效果了. public class WaveView extends View { private static final String TAG = "WaveView"; private

  • Android自定义view仿QQ的Tab按钮动画效果(示例代码)

    话不多说 先上效果图 实现其实很简单,先用两张图 一张是背景的图,一张是笑脸的图片,笑脸的图片是白色,可能看不出来.实现思路:主要是再触摸view的时候同时移动这两个图片,但是移动的距离不一样,造成的错位感,代码很简单: import android.content.Context import android.graphics.* import android.util.AttributeSet import android.view.MotionEvent import android.vi

  • Android自定义WaveProgressView实现水波纹加载需求

    先看效果图: 你可以定义成你项目的logo图片,可以设置水波颜色.波长.波宽.字体大小.颜色.进度条的最大值,当前进度值,还可以设置波纹震动的快慢.当设置一个进度不变的时候,打开时还有一个动画填满的效果(比如第二个流量显示,这里图片没有截出这个效果). 源码地址 1. 如何使用 1.1 在布局文件中 添加自定义控件: <cn.fanrunqi.waveprogressview.WaveProgressView android:id="@+id/waveProgressbar" a

随机推荐

其他