Android 自定義球型水波紋帶圓弧進(jìn)度效果(實(shí)例代碼)
需求
如下,實(shí)現(xiàn)一個(gè)圓形水波紋,帶進(jìn)度,兩層水波紋需要漸變顯示,且外圍有一個(gè)圓弧進(jìn)度。
思路
外圍圓弧進(jìn)度:可以通過(guò)canvas.drawArc()實(shí)現(xiàn)。由于圓弧需要實(shí)現(xiàn)漸變,可以通過(guò)給畫筆設(shè)置shader(SweepGradient)渲染,為了保證圓弧起始的顏色值始終一致,需要?jiǎng)討B(tài)調(diào)整shader的參數(shù)。具體參見
SweepGradient(centerX.toFloat(), centerY.toFloat(), circleColors[0], floatArrayOf(0f, value / 100f))
第四個(gè)參數(shù)需要根據(jù)當(dāng)前進(jìn)度填寫對(duì)應(yīng)數(shù)據(jù)比例。不懂的同學(xué)可以自行百度查閱。
水波紋的實(shí)現(xiàn):直接使用貝塞爾曲線Path.quadTo()實(shí)現(xiàn),通過(guò)拉伸水平直線繪制波浪效果。可以通過(guò)控制拉伸點(diǎn)(waveAmplitude)距離水平線的高度,達(dá)到波浪高度的控制。至于波浪的移動(dòng),可以通過(guò)移動(dòng)平移水平線的起始位置來(lái)實(shí)現(xiàn),在使用動(dòng)畫循環(huán)即可,為了能夠穩(wěn)定的顯示,繪制波浪時(shí)需要嚴(yán)格繪制整數(shù)倍周期的波浪。
園形的實(shí)現(xiàn):繪制一個(gè)完整的圓形,然后通過(guò)Path.op()合并裁剪水波紋path。注意點(diǎn)就是Android6有個(gè)坑,使用該方法會(huì)有明顯的抖動(dòng),為了解決該問(wèn)題,我的做法是多畫一層圓弧以掩蓋此抖動(dòng)。
生命周期的控制:為了減少某些時(shí)刻CPU的損耗,通過(guò)控制變量自定義lifeDelegate(基于kotlin的代理模式實(shí)現(xiàn))來(lái)控制動(dòng)畫的開始暫停。由于筆者使用的框架基于MVVM,所以代碼就沒(méi)有使用attrs控制屬性,這里就不做過(guò)多的修改了。
整體實(shí)現(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坐標(biāo)
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度開啟漸變,由于線條頭部有個(gè)小圓圈會(huì)導(dǎo)致顯示差異,因此從88度開始繪制
private val sweepMatrix by lazy {
val matrix = Matrix()
matrix.setRotate(88f, centerX.toFloat(), centerY.toFloat())
matrix
}
//進(jìn)度 0-100
var percent = 0
set(value) {
field = value
waveWaterLevelRatio = value / 100f
//y = -4 * x2 + 4x拋物線計(jì)算振幅,水波紋振幅規(guī)律更加真實(shí)
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
//動(dòng)畫
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()
}
}
//設(shè)置中間進(jìn)度文本的字體大小
fun setPercentSize(size: Float) {
percentSize = size
invalidate()
}
//設(shè)置中間提示文本的字體大小
fun setGreedSize(size: Float) {
greedSize = size
invalidate()
}
//設(shè)置文本顏色
fun setTextColor(color: Int) {
textColor = color
textPaint.color = textColor
invalidate()
}
//設(shè)置外圍圓圈的寬度
fun setOuterStrokeWidth(width: Float) {
outerStrokeWidth = width
outerCirclePaint.strokeWidth = outerStrokeWidth
outerNormalCirclePaint.strokeWidth = outerStrokeWidth
invalidate()
}
//設(shè)置內(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()
}
//當(dāng)前窗口銷毀時(shí),回收動(dòng)畫資源
override fun onDetachedFromWindow() {
onDestroy()
super.onDetachedFromWindow()
}
override fun onDraw(canvas: Canvas) {
drawCircle(canvas)
drawWave(canvas)
drawText(canvas)
}
private fun drawWave(canvas: Canvas) {
//波浪當(dāng)前高度
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設(shè)置Path.op存在明顯抖動(dòng),因此多畫一圈圓環(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) {
//繪制進(jìn)度文字
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) {
//繪制外圍進(jìn)度圓圈
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 自定義球型水波紋帶圓弧進(jìn)度效果(實(shí)例代碼),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)我們網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
上一篇:Android實(shí)現(xiàn)倒計(jì)時(shí)的按鈕效果
欄 目:Android
下一篇:Android 簡(jiǎn)單實(shí)現(xiàn)倒計(jì)時(shí)功能
本文標(biāo)題:Android 自定義球型水波紋帶圓弧進(jìn)度效果(實(shí)例代碼)
本文地址:http://www.jygsgssxh.com/a1/Android/9019.html
您可能感興趣的文章
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方法詳解
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)登錄
- 01-10android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代碼
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10Emoji表情在Android JNI中的兼容性問(wèn)題詳解
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10android開發(fā)環(huán)境中SDK文件夾下的所需內(nèi)容詳解


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方
- 01-10android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10Emoji表情在Android JNI中的兼容性問(wèn)題詳
隨機(jī)閱讀
- 01-10delphi制作wav文件的方法
- 04-02jquery與jsp,用jquery
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什


