您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“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 < 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如何自定義球型水波紋帶圓弧進度效果”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。