热门标签 | HotTags
当前位置:  开发笔记 > 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, 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 

总结

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


推荐阅读
  • 本文探讨了Android系统中联系人数据库的设计,特别是AbstractContactsProvider类的作用与实现。文章提供了对源代码的详细分析,并解释了该类如何支持跨数据库操作及事务处理。源代码可从官方Android网站下载。 ... [详细]
  • 本文将介绍几款常用的搜索引擎,包括Google、百度、搜狗和去哪儿网,旨在为用户提供更多高效的网络搜索工具。所有推荐的搜索引擎均为免费服务。 ... [详细]
  • 本文详细介绍了PHP中的几种超全局变量,包括$GLOBAL、$_SERVER、$_POST、$_GET等,并探讨了AJAX的工作原理及其优缺点。通过具体示例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 本文详细介绍了如何使用Rufus工具制作一个兼容UEFI启动模式的Windows Server 2008 R2安装U盘,包括必要的软件和步骤。 ... [详细]
  • 本文介绍如何使用 Python 计算两个时间戳之间的时间差,并将其转换为毫秒。示例代码展示了如何通过 `time` 和 `datetime` 模块实现这一功能。 ... [详细]
  • 本文介绍了如何通过 ADB 命令行工具启动和停止 Android 应用。通过简单的命令,您可以轻松地控制设备上的应用运行状态。 ... [详细]
  • 使用 ModelAttribute 实现页面数据自动填充
    本文介绍了如何利用 Spring MVC 中的 ModelAttribute 注解,在页面跳转后自动填充表单数据。主要探讨了两种实现方法及其背后的原理。 ... [详细]
  • 使用REM和媒体查询实现响应式布局
    本文介绍如何利用REM单位和媒体查询(Media Queries)来创建适应不同屏幕尺寸的网页布局。通过具体示例,展示在不同屏幕宽度下如何调整页面元素的样式。 ... [详细]
  • SPFA算法详解与应用
    当图中包含负权边时,传统的最短路径算法如Dijkstra不再适用,而Bellman-Ford算法虽然能解决问题,但其时间复杂度过高。SPFA算法作为一种改进的Bellman-Ford算法,能够在多数情况下提供更高效的解决方案。本文将详细介绍SPFA算法的原理、实现步骤及其应用场景。 ... [详细]
  • 本文详细对比了HashMap和HashTable在多线程环境下的安全性、对null值的支持、性能表现以及方法同步等方面的特点,帮助开发者根据具体需求选择合适的数据结构。 ... [详细]
  • 本文介绍了一种在 Android 开发中动态修改 strings.xml 文件中字符串值的有效方法。通过使用占位符,开发者可以在运行时根据需要填充具体的值,从而提高应用的灵活性和可维护性。 ... [详细]
  • 神策数据分析基础
    本文介绍了基于用户行为的数据分析方法,包括业务问题的提出与定义、具体行为的识别及统计分析流程。同时,详细阐述了如何利用事件模型(Event Model)来描述用户行为,以及在实际应用中的案例分析。 ... [详细]
  • 2023年1月28日网络安全热点
    涵盖最新的网络安全动态,包括OpenSSH和WordPress的安全更新、VirtualBox提权漏洞、以及谷歌推出的新证书验证机制等内容。 ... [详细]
  • Docker基础入门与环境配置指南
    本文介绍了Docker——一款用Go语言编写的开源应用程序容器引擎。通过Docker,用户能够将应用及其依赖打包进容器内,实现高效、轻量级的虚拟化。容器之间采用沙箱机制,确保彼此隔离且资源消耗低。 ... [详细]
  • 本文列举了构建和运行 Struts2 应用程序所需的核心 JAR 文件,包括文件上传、日志记录、模板引擎等关键组件。 ... [详细]
author-avatar
阿尔及利亚奥兰叉
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有