Android 自定義球型水波紋帶圓弧進度效果(實例代碼)
需求
如下,實現(xiàn)一個圓形水波紋,帶進度,兩層水波紋需要漸變顯示,且外圍有一個圓弧進度。
思路
外圍圓弧進度:可以通過canvas.drawArc()
實現(xiàn)。由于圓弧需要實現(xiàn)漸變,可以通過給畫筆設置shader(SweepGradient)
渲染,為了保證圓弧起始的顏色值始終一致,需要動態(tài)調(diào)整shader的參數(shù)。具體參見
SweepGradient(centerX.toFloat(), centerY.toFloat(), circleColors[0], floatArrayOf(0f, value / 100f))
第四個參數(shù)需要根據(jù)當前進度填寫對應數(shù)據(jù)比例。不懂的同學可以自行百度查閱。
水波紋的實現(xiàn):直接使用貝塞爾曲線Path.quadTo()實現(xiàn),通過拉伸水平直線繪制波浪效果??梢酝ㄟ^控制拉伸點(waveAmplitude)距離水平線的高度,達到波浪高度的控制。至于波浪的移動,可以通過移動平移水平線的起始位置來實現(xiàn),在使用動畫循環(huán)即可,為了能夠穩(wěn)定的顯示,繪制波浪時需要嚴格繪制整數(shù)倍周期的波浪。
園形的實現(xiàn):繪制一個完整的圓形,然后通過Path.op()合并裁剪水波紋path。注意點就是Android6有個坑,使用該方法會有明顯的抖動,為了解決該問題,我的做法是多畫一層圓弧以掩蓋此抖動。
生命周期的控制:為了減少某些時刻CPU的損耗,通過控制變量自定義lifeDelegate(基于kotlin的代理模式實現(xiàn))來控制動畫的開始暫停。由于筆者使用的框架基于MVVM,所以代碼就沒有使用attrs控制屬性,這里就不做過多的修改了。
整體實現(xiàn)
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//外圈圓環(huán)的半徑 private var innerRadius = 250f//內(nèi)部圓圈的半徑 private var radiusDist = 50f//內(nèi)外圓圈的半徑差距 private var fWaveShader: LinearGradient? = null private var sWaveShader: LinearGradient? = null private var wavePath = Path() private var waveCirclePath = Path() private val waveNum = 2 //波浪的漸變顏色數(shù)組 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")) ) } //外圍圓環(huán)的漸變色 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拋物線計算振幅,水波紋振幅規(guī)律更加真實 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() } //設置內(nèi)圓半徑 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存在明顯抖動,因此多畫一圈圓環(huán) 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 ) } }
總結(jié)
以上所述是小編給大家介紹的Android 自定義球型水波紋帶圓弧進度效果(實例代碼),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對我們網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!
您可能感興趣的文章
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實現(xiàn)雙擊返回鍵退出應用實現(xiàn)方法詳解
- 01-10android實現(xiàn)記住用戶名和密碼以及自動登錄
- 01-10android實現(xiàn)簡單計算器功能
- 01-10Android 友盟第三方登錄與分享的實現(xiàn)代碼
- 01-10C++自定義API函數(shù)實現(xiàn)大數(shù)相乘算法
- 01-10android實現(xiàn)指紋識別功能
- 01-10Emoji表情在Android JNI中的兼容性問題詳解
- 01-10Android實現(xiàn)圓形漸變加載進度條
- 01-10android開發(fā)環(huán)境中SDK文件夾下的所需內(nèi)容詳解


閱讀排行
本欄相關
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實現(xiàn)雙擊返回鍵退出應用實現(xiàn)方
- 01-10android實現(xiàn)簡單計算器功能
- 01-10android實現(xiàn)記住用戶名和密碼以及自動
- 01-10C++自定義API函數(shù)實現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實現(xiàn)代
- 01-10android實現(xiàn)指紋識別功能
- 01-10如何給Flutter界面切換實現(xiàn)點特效
- 01-10Android實現(xiàn)圓形漸變加載進度條
- 01-10Emoji表情在Android JNI中的兼容性問題詳
隨機閱讀
- 01-10delphi制作wav文件的方法
- 04-02jquery與jsp,用jquery
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-11ajax實現(xiàn)頁面的局部加載
- 01-10C#中split用法實例總結(jié)
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 08-05DEDE織夢data目錄下的sessions文件夾有什