千家信息网

Android怎么实现球型水波纹带圆弧进度效果

发表于:2025-11-09 作者:千家信息网编辑
千家信息网最后更新 2025年11月09日,今天小编给大家分享一下Android怎么实现球型水波纹带圆弧进度效果的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收
千家信息网最后更新 2025年11月09日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() privateval waveNum = 2 //波浪的渐变颜色数组 privateval 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"))  ) } //外围圆环的渐变色 privateval 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"))  ) } privateval 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 //外围圆圈的画笔 privateval outerCirclePaint by lazy {  val paint = Paint()  paint.strokeWidth = 20f  paint.strokeCap = Paint.Cap.ROUND  paint.style = Paint.Style.STROKE  paint.isAntiAlias = true  paint } privateval outerNormalCirclePaint by lazy {  val paint = Paint()  paint.strokeWidth = 20f  paint.color = Color.parseColor("#FFF2F3F3")  paint.style = Paint.Style.STROKE  paint.isAntiAlias = true  paint } privateval bgCirclePaint by lazy {  val paint = Paint()  paint.color = Color.parseColor("#FFF6FAFF")  paint.style = Paint.Style.FILL  paint.isAntiAlias = true  paint } privateval textPaint by lazy {  val paint = Paint()  paint.style = Paint.Style.FILL  paint.textAlign = Paint.Align.CENTER  paint.isFakeBoldText = true  paint.isAntiAlias = true  paint } privateval ringPaint by lazy {  val paint = Paint()  paint.style = Paint.Style.STROKE  paint.color = Color.WHITE  paint.isAntiAlias = true  paint } //外围圆圈所在的矩形 privateval 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度开始绘制 privateval 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) * maxWaveAmplitudeval 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()  } privateval 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 //动画 privateval 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 } privateval 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怎么实现球型水波纹带圆弧进度效果"这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注行业资讯频道。

波浪 进度 圆圈 圆弧 波纹 高度 控制 位置 动画 半径 可以通过 大小 振幅 文本 知识 篇文章 颜色 起始 效果 圆环 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 快宝网络技术有限公司招聘 如何获得股票在线数据库 2018年全球网络安全报告 实验要获取什么的数据库 工行网络安全知识 计算机三级网络技术题库破解 金蝶软件开发笔试题 大连豪森软件开发有限公司简介 德立信软件开发地址 宝山区推广软件开发销售方法 网络安全要花多少钱 网络安全第五十九条 数据库动态更新方法周期 西安软件开发两年薪资多少钱 企业信息网络安全内容 软件开发出口即征即退 先卸载了用友数据库 学习计算机网络技术前景 服务管理器 服务器为空 手机软件开发需要哪些技术 重启服务器方法视频 网络安全周线上知识问答2021 怎么查看服务器管理员密码 康乐虚拟主机清除数据库 合肥微晶科技互联网 郴州智能软件开发哪家好 网络安全法 为了向未成年人 网络安全宣传周在几月 学习计算机网络技术前景 上海市人口普查数据库
0