需求
如下,实现一个圆形水波纹,带进度,两层水波纹需要渐变显示,且外围有一个圆弧进度。
思路
外围圆弧进度:可以通过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,wavecolors[1],Shader.TileMode.CLAMP ) SweepGradIEnt( centerX.tofloat(),value / 100f) ) } in 47..54 -> { fWaveShader = linearGradIEnt( 0f,wavecolors[2],wavecolors[3],circlecolors[1],value / 100f) ) } else -> { fWaveShader = linearGradIEnt( 0f,wavecolors[4],wavecolors[5],circlecolors[2],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(),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(),innerRadius + ringWIDth / 4,ringPaint) } private fun drawText(canvas: Canvas) { //绘制进度文字 textPaint.isFakeBoldText = true textPaint.textSize = percentSize canvas.drawText( percent.toString(),centerY.tofloat() + textPaint.textSize / 2,textPaint ) textPaint.isFakeBoldText = false textPaint.textSize = greedSize canvas.drawText( greedTip,centerY.tofloat() - textPaint.textSize * 2,textPaint ) } private fun drawCircle(canvas: Canvas) { //绘制外围进度圆圈 canvas.drawArc(outerCircleRectf,360f,false,outernormalCirclePaint) canvas.drawArc(outerCircleRectf,90f,percent * 3.6f,outerCirclePaint) canvas.drawCircle( centerX.tofloat(),bgCirclePaint ) }}
总结
以上所述是小编给大家介绍的AndroID 自定义球型水波纹带圆弧进度效果(实例代码),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
以上是内存溢出为你收集整理的Android 自定义球型水波纹带圆弧进度效果(实例代码)全部内容,希望文章能够帮你解决Android 自定义球型水波纹带圆弧进度效果(实例代码)所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)