千家信息网

Android如何自定义球型水波纹带圆弧进度效果

发表于:2025-02-19 作者:千家信息网编辑
千家信息网最后更新 2025年02月19日,这篇文章主要为大家展示了"Android如何自定义球型水波纹带圆弧进度效果",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Android如何自定义球型水波纹
千家信息网最后更新 2025年02月19日Android如何自定义球型水波纹带圆弧进度效果

这篇文章主要为大家展示了"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安全错误 数据库的锁怎样保障安全 白龙飞网络安全 地平线零之曙光服务器位置 基层网络安全自查报告 南京小型软件开发售后服务 服务器上的外网接口 oracle数据库 监控 重要保障期间网络安全 幼儿园关于网络安全的重要指示 崩坏32020都有哪些服务器 中国网络安全法2050 手游版方舟怎么开自己的服务器 考研软件开发需要什么专业课 合肥晶网络软件开发公司 纳入投资统计数据库 笔记本数据库编程和设计哪个好 远程畅玩连接服务器失败 服务器硬件国际品牌有哪些 网络安全防护盒 盈环网络技术有限公司官网 服务器管理口ip忘了怎么办 本地服务器固定ip域名备案 学生网络安全班会相片 西安市未央区青易网络技术服务部 清查数据库和服务器 日本软件开发用什么语言 河北六维网络技术有限公司 战争雷霆空战服务器无响应 湖南通讯软件开发品质保障 关于网络安全隐患整改的函 湖北软件开发电话
0