又是更新blog的一天,最近项目中遇到一个需求,绘制一个渐变色的仪表盘去展示某个行情当前的热度,为用户提供当前市场热度更直观的一个大概参考,需要用到自定义View的相关知识,在这里做一个总结,欢迎一起学习和讨论~
最终效果图:
变量声明
- 声明画笔,颜色,刻度数,属性动画,进度条宽度,表盘矩形区域,文本大小,必要的偏移量,表盘的开始和过渡角度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28var progressColor = intArrayOf() // 渐变色开始颜色
var progressBackgroundColor = R.color.emphasis16 // 进度条背景颜色
var textColor = R.color.primary // 文字颜色
var tickScaleColor = R.color.emphasis8 // 普通刻度线颜色
var groupScaleColor = R.color.emphasis38 // 分组刻度线颜色
var progressStrokeWidth = 24f // 进度条宽度
var paintProgressBackground = Paint() // 进度条背景画笔
private var paintProgress = Paint() // 进度条画笔
var paintText = Paint() // 文字画笔
private var paintNum = Paint() // 刻度画笔
var rect = RectF() // 表盘矩形区域
private var viewWidth = 0 // 宽度
private var viewHeight = 0 // 高度
var percent = 0f // 百分比
var oldPercent = 0f // 过去的百分比
var textSize = 100f // 文本大小
private var valueAnimator: ValueAnimator? = null // 属性动画
var animatorDuration = 0L // 动画时长
private var groupNum = 5 // 分组数
private var ticksNum = 6 // 每组刻度数
var pointerWidth = 15f // 指针的宽度
private var ticksCount = groupNum * ticksNum + 1// 总刻度数
companion object {
var OFFSET = 30f // 偏移量
var START_ARC = 150f // 开始角度
var DURING_ARC = 240f // 过渡调度
}
初始化逻辑
- 获取xml的配置进行变量初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.dashboard)
progressBackgroundColor = typedArray.getColor(
R.styleable.dashboard_progressBackgroundColor,
ContextCompat.getColor(context, progressBackgroundColor)
)
progressStrokeWidth = typedArray.getDimension(
R.styleable.dashboard_progressStrokeWidth,
progressStrokeWidth
)
textSize = typedArray.getDimension(
R.styleable.dashboard_textSize,
textSize
)
textColor = typedArray.getColor(R.styleable.dashboard_textColor, textColor)
tickScaleColor =
typedArray.getColor(R.styleable.dashboard_tickScaleColor, tickScaleColor)
groupScaleColor =
typedArray.getColor(R.styleable.dashboard_groupScaleColor, groupScaleColor)
groupNum = typedArray.getInt(R.styleable.dashboard_groupNum, groupNum)
ticksNum = typedArray.getInt(R.styleable.dashboard_ticksNum, ticksNum)
pointerWidth =
typedArray.getDimension(R.styleable.dashboard_pointerWidth, pointerWidth)
val colorsId = typedArray.getResourceId(R.styleable.dashboard_progressColors, 0)
progressColor =typedArray.resources.getIntArray(colorsId)
ticksCount = groupNum * ticksNum + 1
typedArray.recycle()
OFFSET = progressStrokeWidth + 10f
initPaint()
}
/**
* 初始化画笔
*/
private fun initPaint() {
paintProgressBackground.isAntiAlias = true
paintProgressBackground.strokeWidth = progressStrokeWidth
paintProgressBackground.style = Paint.Style.STROKE
paintProgressBackground.color = progressBackgroundColor
paintProgressBackground.isDither = true
paintProgress.isAntiAlias = true
paintProgress.strokeWidth = progressStrokeWidth
paintProgress.style = Paint.Style.STROKE
paintProgress.isDither = true
paintText.isAntiAlias = true
paintText.color = textColor
paintText.strokeWidth = 1F
paintText.style = Paint.Style.FILL
paintText.isDither = true
paintNum.isAntiAlias = true
paintNum.strokeWidth = 3f
paintNum.style = Paint.Style.FILL
paintNum.isDither = true
}声明xml配置属性
1
2
3
4
5
6
7
8
9
10
11
12<declare-styleable name="dashboard">
<attr name="progressStrokeWidth" format="dimension" />
<attr name="progressBackgroundColor" format="color" />
<attr name="textColor" format="color" />
<attr name="tickScaleColor" format="color" />
<attr name="groupScaleColor" format="color" />
<attr name="groupNum" format="integer" />
<attr name="ticksNum" format="integer" />
<attr name="pointerWidth" format="dimension" />
<attr name="textSize" format="dimension" />
<attr name="progressColors" format="reference"/>
</declare-styleable>获取自定义View宽高,初始化渐变进度条
回顾一下View的生命周期
1
activity.onCreate -> onFinishInflate -> activity.onStart -> activity.onResume -> onAttachedToWindow -> onWindowVisibilityChanged -> onVisibilityChanged -> onMeasure -> onSizeChanged -> onLayout -> onDraw -> onWindowFocusChanged -> activity.onPause -> onWindowFocusChanged -> onWindowVisibilityChanged -> activity.onStop -> onVisibilityChanged -> activity.onDestroy -> onDetachedFromWindow
- 重写onSizeChanged,此时自定义View已经完成测量可以拿到当前自定义View的宽高
- 初始化指针宽度,当前变盘的矩形区域,以0,0点为原点,左边上角的坐标,x为-(View宽度 / 2)+ 偏移量 + paddingLeft,y为- (view高度 / 2) + 偏移量 + paddingTop,右下角的坐标,x为(View宽度 / 2)- 偏移量 - paddingRight,y为(view高度 / 2) - 偏移量 - paddingBottom,渐变色采用以原点为中心的90度渐变,初始化Shader的子类SweepGradient并设置一个以原点为中心的90度旋转操作后的矩阵,再设置这个Shader为画笔的Shader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = width
viewHeight = height
pointerWidth = (viewWidth / 40).toFloat()
initShader()
}
/**
* 初始化渐变颜色
*/
private fun initShader() {
rect.set(
(-viewWidth / 2) + OFFSET + paddingLeft, paddingTop - (viewHeight / 2) + OFFSET,
(viewWidth / 2) - paddingRight - OFFSET,
(viewWidth / 2) - paddingBottom - OFFSET
)
val shader = SweepGradient(
0f,
0f,
progressColor,
null
)
val rotate = 90f
val gradientMatrix = Matrix()
gradientMatrix.preRotate(rotate, 0f, 0f)
shader.setLocalMatrix(gradientMatrix)
paintProgress.shader = shader
}测量View的宽高
- 根据当前的模式和大小决定View大小,如果是明确指明宽高的大小就用当前声明的值,否则就设置一个固定值使得宽高一致防止自定义View形变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val realWidth = startMeasure(widthMeasureSpec)
val realHeight = startMeasure(heightMeasureSpec)
setMeasuredDimension(realWidth, realHeight)
}
fun startMeasure(msSpec: Int): Int {
val mode = MeasureSpec.getMode(msSpec)
val size = MeasureSpec.getSize(msSpec)
return if (mode == MeasureSpec.EXACTLY) {
size
} else {
Util.dp2px(200)
}
}重写绘制方法
- 当前的仪表盘的绘制逻辑分为绘制表盘刻度,绘制进度条,绘制文本,绘制指针,canvas的原点为左上角,为了方便计算在绘制前需要把原点移动到canvas的中心,所以x方向平移(View宽度 / 2),y方向平移(View高度 / 2)
1
2
3
4
5
6
7
8override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.translate((viewWidth / 2).toFloat(), (viewHeight / 2).toFloat())
drawPanel(canvas)
drawProgress(canvas, percent)
drawText(canvas, percent)
drawPointer(canvas, percent)
}绘制表盘刻度
- 以0,0点为圆心,绘制起始点逆时针旋转-120度,计算刻度的y坐标为 -(View高度 / 2) + 偏移量 + 进度条宽度,计算每个刻度的旋转角度为过渡角度 / (总刻度数 - 1),再以总刻度数进行循环绘制刻度线条,判断当前下标是否为新一组开始刻度的位置,区分普通刻度和分割刻度并设置不同的颜色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
* 绘制刻度
* @param canvas Canvas?
*/
fun drawPanel(canvas: Canvas?) {
canvas?.save()
canvas?.rotate(-(180 - START_ARC + 90), 0f, 0f)
val numY = -viewHeight / 2 + OFFSET + progressStrokeWidth
val angle = DURING_ARC / ((ticksCount - 1) * 1.0f)
for (i in 0 until ticksCount) {
canvas?.save()
canvas?.rotate(angle * i, 0f, 0f)
if (i == 0 || i % groupNum == 0) {
paintNum.color = groupScaleColor
} else {
paintNum.color = tickScaleColor
}
canvas?.drawLine(0f, numY + 2, 0f, numY + (pointerWidth * 2) + 5, paintNum)
canvas?.restore()
}
canvas?.restore()
}绘制进度条
- 先绘制背景进度条,绘制一个圆弧,绘制区域为表盘矩形区域,设置开始角度和过渡角度
- 绘制进度条,判断当前的进度百分比如果大于1则为1,过滤掉其他异常情况,如果百分比大于0绘制一个渐变色圆弧,根据当前百分比和过渡角度计算当前需要绘制的渐变色过渡角度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* 绘制进度
* @param canvas Canvas?
* @param percent Float
*/
private fun drawProgress(canvas: Canvas?, percent: Float) {
canvas?.drawArc(rect, START_ARC, DURING_ARC, false, paintProgressBackground)
var curPercent = percent
if (curPercent > 1.0f) {
curPercent = 1.0f
}
if (curPercent > 0.0f) {
canvas?.drawArc(rect, START_ARC, percent * DURING_ARC, false, paintProgress)
}
}绘制文本
- 根据当前的进度百分比设置文本的内容和颜色,根据当前的View宽度计算一个Y方向的偏移量,如果百分比小于0.2展示“Very Cold”文本,渐变色数组中取第一个颜色,绘制文本的x坐标为文本宽度/2,第一个词y坐标为1.4倍偏移量,第二个词为2.4倍偏移量,如果百分比大于等于0.2并且小于0.4展示“Cold”文本,y坐标为2.2倍偏移量,如果百分比大于等于0.4并且小于0.6展示“Normal”文本,y坐标为2.2倍偏移量,如果百分比大于等于0.6并且小于0.8展示“Hot”文本,y坐标为2.2倍偏移量,如果是剩下的情况展示“Very Hot”文本,第一个词y坐标为1.4倍偏移量,第二个词为2.4倍偏移量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81/**
* 绘制文本
* @param canvas Canvas?
* @param percent Float
*/
fun drawText(canvas: Canvas?, percent: Float) {
val offsetY = viewWidth / 8
paintText.textSize = textSize
if (percent < 0.2f) {
val subTitle1 = "Very"
paintText.color = progressColor[0]
canvas?.drawText(
subTitle1,
-paintText.measureText(subTitle1) / 2,
offsetY * 1.4f,
paintText
)
val subTitle2 = "Cold"
canvas?.drawText(
subTitle2,
-paintText.measureText(subTitle2) / 2,
offsetY * 2.4f,
paintText
)
} else if (percent >= 0.2f && percent < 0.4f) {
val subTitle = "Cold"
paintText.color = progressColor[0]
canvas?.drawText(
subTitle,
-paintText.measureText(subTitle) / 2,
offsetY * 2.2f,
paintText
)
} else if (percent >= 0.4f && percent < 0.6f) {
val subTitle = "Normal"
paintText.color = progressColor[2]
canvas?.drawText(
subTitle,
-paintText.measureText(subTitle) / 2,
offsetY * 2.2f,
paintText
)
} else if (percent >= 0.6f && percent < 0.8f) {
val subTitle = "Hot"
paintText.color = progressColor[4]
canvas?.drawText(
subTitle,
-paintText.measureText(subTitle) / 2,
offsetY * 2.2f,
paintText
)
} else {
val subTitle1 = "Very"
paintText.color = progressColor[4]
canvas?.drawText(
subTitle1,
-paintText.measureText(subTitle1) / 2,
offsetY * 1.4f,
paintText
)
val subTitle2 = "Hot"
canvas?.drawText(
subTitle2,
-paintText.measureText(subTitle2) / 2,
offsetY * 2.4f,
paintText
)
}
paintText.textSize = (textSize * 1.3).toFloat()
val text = (percent * 100).toInt().toString()
canvas?.drawText(
text,
-paintText.measureText(text) / 2,
- textSize / 5f,
paintText
)
}绘制指针
- 绘制一个三角形的指针,根据当前的百分比计算指针所在位置的旋转角度,再计算指针所在位置需要绘制三角形的路径,从(0, view的高度/2 - 偏移量 - 进度条宽度)为起点,绘制一个倒三角形,绘制右边的线段和左边的线段再绘制顶部的线段,以垂直向下为0度,旋转范围是-300到-60并设置填充模式绘制这条路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/**
* 绘制指针
* @param canvas Canvas?
* @param percent Float
*/
private fun drawPointer(canvas: Canvas?, percent: Float) {
canvas?.save()
val angle = DURING_ARC * (percent - 0.5f) - 180
canvas?.rotate(angle, 0f, 0f)
val pointer = Path()
pointer.moveTo(0f, viewHeight / 2 - OFFSET - progressStrokeWidth)
pointer.lineTo(
pointerWidth,
viewHeight / 2 - OFFSET - progressStrokeWidth - (pointerWidth * 2)
)
pointer.lineTo(
-pointerWidth,
viewHeight / 2 - OFFSET - progressStrokeWidth - (pointerWidth * 2)
)
pointer.lineTo(0f, viewHeight / 2 - OFFSET - progressStrokeWidth)
pointer.close()
pointer.fillType = Path.FillType.EVEN_ODD
canvas?.drawPath(pointer, paintText)
canvas?.restore()
}
设置百分比和动画
- 传入当前的百分比范围0~1,初始化属性动画设置属性值变化监听刷新当前百分比,根据上一次的百分比和当前百分比计算动画时间,设置动画开始结束的百分比数值,设置动画结束监听刷新上一次百分比数值并做边界判断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/**
* 设置当前百分比
* @param curPercent Float 范围0~1
*/
fun setProgress(curPercent: Float) {
if (valueAnimator?.isRunning == true) {
valueAnimator?.cancel()
}
animatorDuration = (abs(curPercent - oldPercent) * 20).toLong()
valueAnimator = ValueAnimator.ofFloat(oldPercent, curPercent).setDuration(animatorDuration)
valueAnimator?.addUpdateListener {
percent = it.animatedValue as Float
invalidate()
}
valueAnimator?.interpolator = LinearInterpolator()
valueAnimator?.addListener(onEnd = {
oldPercent = curPercent
if (percent < 0.0f) {
percent = 0.0f
invalidate()
}
if (percent > 1f) {
percent = 1f
invalidate()
}
})
valueAnimator?.start()
}使用
- xml声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<com.example.testapp.DashboardView
android:id="@+id/view_dashboard"
android:layout_width="200dp"
android:layout_height="200dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="6dp"
android:layout_marginTop="8dp"
app:groupNum="5"
app:groupScaleColor="@color/emphasis38"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:pointerWidth="2dp"
app:progressBackgroundColor="@color/emphasis8"
app:progressStrokeWidth="6dp"
app:progressColors="@array/dashboardColors"
app:textSize="24sp"
app:tickScaleColor="@color/emphasis8"
app:ticksNum="6" /> - 设置当前百分比最后附上完整代码:
1
dashboardView.setProgress((float) i / 100)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351package com.example.testapp
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.core.animation.addListener
import androidx.core.content.ContextCompat
import kotlin.math.abs
class DashboardView constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
View(context, attrs, defStyleAttr) {
var progressColor = intArrayOf() // 渐变色开始颜色
var progressBackgroundColor = R.color.emphasis16 // 进度条背景颜色
var textColor = R.color.primary // 文字颜色
var tickScaleColor = R.color.emphasis8 // 普通刻度线颜色
var groupScaleColor = R.color.emphasis38 // 分组刻度线颜色
var progressStrokeWidth = 24f // 进度条宽度
var paintProgressBackground = Paint() // 进度条背景画笔
private var paintProgress = Paint() // 进度条画笔
var paintText = Paint() // 文字画笔
private var paintNum = Paint() // 刻度画笔
var rect = RectF() // 表盘矩形区域
private var viewWidth = 0 // 宽度
private var viewHeight = 0 // 高度
var percent = 0f // 百分比
var oldPercent = 0f // 过去的百分比
var textSize = 100f // 文本大小
private var valueAnimator: ValueAnimator? = null // 属性动画
var animatorDuration = 0L // 动画时长
private var groupNum = 5 // 分组数
private var ticksNum = 6 // 每组刻度数
var pointerWidth = 15f // 指针的宽度
private var ticksCount = groupNum * ticksNum + 1// 总刻度数
companion object {
var OFFSET = 30f // 偏移量
var START_ARC = 150f // 开始角度
var DURING_ARC = 240f // 过渡调度
}
init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.dashboard)
progressBackgroundColor = typedArray.getColor(
R.styleable.dashboard_progressBackgroundColor,
ContextCompat.getColor(context, progressBackgroundColor)
)
progressStrokeWidth = typedArray.getDimension(
R.styleable.dashboard_progressStrokeWidth,
progressStrokeWidth
)
textSize = typedArray.getDimension(
R.styleable.dashboard_textSize,
textSize
)
textColor = typedArray.getColor(R.styleable.dashboard_textColor, textColor)
tickScaleColor =
typedArray.getColor(R.styleable.dashboard_tickScaleColor, tickScaleColor)
groupScaleColor =
typedArray.getColor(R.styleable.dashboard_groupScaleColor, groupScaleColor)
groupNum = typedArray.getInt(R.styleable.dashboard_groupNum, groupNum)
ticksNum = typedArray.getInt(R.styleable.dashboard_ticksNum, ticksNum)
pointerWidth =
typedArray.getDimension(R.styleable.dashboard_pointerWidth, pointerWidth)
val colorsId = typedArray.getResourceId(R.styleable.dashboard_progressColors, 0)
progressColor =typedArray.resources.getIntArray(colorsId)
ticksCount = groupNum * ticksNum + 1
typedArray.recycle()
OFFSET = progressStrokeWidth + 10f
initPaint()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val realWidth = startMeasure(widthMeasureSpec)
val realHeight = startMeasure(heightMeasureSpec)
setMeasuredDimension(realWidth, realHeight)
}
fun startMeasure(msSpec: Int): Int {
val mode = MeasureSpec.getMode(msSpec)
val size = MeasureSpec.getSize(msSpec)
return if (mode == MeasureSpec.EXACTLY) {
size
} else {
Util.dp2px(200)
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = width
viewHeight = height
pointerWidth = (viewWidth / 40).toFloat()
initShader()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.translate((viewWidth / 2).toFloat(), (viewHeight / 2).toFloat())
drawPanel(canvas)
drawProgress(canvas, percent)
drawText(canvas, percent)
drawPointer(canvas, percent)
}
/**
* 绘制刻度
* @param canvas Canvas?
*/
fun drawPanel(canvas: Canvas?) {
canvas?.save()
canvas?.rotate(-(180 - START_ARC + 90), 0f, 0f)
val numY = -viewHeight / 2 + OFFSET + progressStrokeWidth
val angle = DURING_ARC / ((ticksCount - 1) * 1.0f)
for (i in 0 until ticksCount) {
canvas?.save()
canvas?.rotate(angle * i, 0f, 0f)
if (i == 0 || i % groupNum == 0) {
paintNum.color = groupScaleColor
} else {
paintNum.color = tickScaleColor
}
canvas?.drawLine(0f, numY + 2, 0f, numY + (pointerWidth * 2) + 5, paintNum)
canvas?.restore()
}
canvas?.restore()
}
/**
* 绘制进度
* @param canvas Canvas?
* @param percent Float
*/
private fun drawProgress(canvas: Canvas?, percent: Float) {
canvas?.drawArc(rect, START_ARC, DURING_ARC, false, paintProgressBackground)
var curPercent = percent
if (curPercent > 1.0f) {
curPercent = 1.0f
}
if (curPercent > 0.0f) {
canvas?.drawArc(rect, START_ARC, percent * DURING_ARC, false, paintProgress)
}
}
/**
* 绘制指针
* @param canvas Canvas?
* @param percent Float
*/
private fun drawPointer(canvas: Canvas?, percent: Float) {
canvas?.save()
val angle = DURING_ARC * (percent - 0.5f) - 180
canvas?.rotate(angle, 0f, 0f)
val pointer = Path()
pointer.moveTo(0f, viewHeight / 2 - OFFSET - progressStrokeWidth)
pointer.lineTo(
pointerWidth,
viewHeight / 2 - OFFSET - progressStrokeWidth - (pointerWidth * 2)
)
pointer.lineTo(
-pointerWidth,
viewHeight / 2 - OFFSET - progressStrokeWidth - (pointerWidth * 2)
)
pointer.lineTo(0f, viewHeight / 2 - OFFSET - progressStrokeWidth)
pointer.close()
pointer.fillType = Path.FillType.EVEN_ODD
canvas?.drawPath(pointer, paintText)
canvas?.restore()
}
/**
* 绘制文本
* @param canvas Canvas?
* @param percent Float
*/
fun drawText(canvas: Canvas?, percent: Float) {
val offsetY = viewWidth / 8
paintText.textSize = textSize
if (percent < 0.2f) {
val subTitle1 = "Very"
paintText.color = progressColor[0]
canvas?.drawText(
subTitle1,
-paintText.measureText(subTitle1) / 2,
offsetY * 1.4f,
paintText
)
val subTitle2 = "Cold"
canvas?.drawText(
subTitle2,
-paintText.measureText(subTitle2) / 2,
offsetY * 2.4f,
paintText
)
} else if (percent >= 0.2f && percent < 0.4f) {
val subTitle = "Cold"
paintText.color = progressColor[0]
canvas?.drawText(
subTitle,
-paintText.measureText(subTitle) / 2,
offsetY * 2.2f,
paintText
)
} else if (percent >= 0.4f && percent < 0.6f) {
val subTitle = "Normal"
paintText.color = progressColor[2]
canvas?.drawText(
subTitle,
-paintText.measureText(subTitle) / 2,
offsetY * 2.2f,
paintText
)
} else if (percent >= 0.6f && percent < 0.8f) {
val subTitle = "Hot"
paintText.color = progressColor[4]
canvas?.drawText(
subTitle,
-paintText.measureText(subTitle) / 2,
offsetY * 2.2f,
paintText
)
} else {
val subTitle1 = "Very"
paintText.color = progressColor[4]
canvas?.drawText(
subTitle1,
-paintText.measureText(subTitle1) / 2,
offsetY * 1.4f,
paintText
)
val subTitle2 = "Hot"
canvas?.drawText(
subTitle2,
-paintText.measureText(subTitle2) / 2,
offsetY * 2.4f,
paintText
)
}
paintText.textSize = (textSize * 1.3).toFloat()
val text = (percent * 100).toInt().toString()
canvas?.drawText(
text,
-paintText.measureText(text) / 2,
- textSize / 5f,
paintText
)
}
/**
* 设置当前百分比
* @param curPercent Float 范围0~1
*/
fun setProgress(curPercent: Float) {
if (valueAnimator?.isRunning == true) {
valueAnimator?.cancel()
}
animatorDuration = (abs(curPercent - oldPercent) * 20).toLong()
valueAnimator = ValueAnimator.ofFloat(oldPercent, curPercent).setDuration(animatorDuration)
valueAnimator?.addUpdateListener {
percent = it.animatedValue as Float
invalidate()
}
valueAnimator?.interpolator = LinearInterpolator()
valueAnimator?.addListener(onEnd = {
oldPercent = curPercent
if (percent < 0.0f) {
percent = 0.0f
invalidate()
}
if (percent > 1f) {
percent = 1f
invalidate()
}
})
valueAnimator?.start()
}
/**
* 设置进度条宽度
* @param dp Int
*/
fun setProgressStroke(dp: Int) {
progressStrokeWidth = Util.dp2px(dp).toFloat()
paintProgress.strokeWidth = progressStrokeWidth
paintProgressBackground.strokeWidth = progressStrokeWidth
invalidate()
}
/**
* 初始化画笔
*/
private fun initPaint() {
paintProgressBackground.isAntiAlias = true
paintProgressBackground.strokeWidth = progressStrokeWidth
paintProgressBackground.style = Paint.Style.STROKE
paintProgressBackground.color = progressBackgroundColor
paintProgressBackground.isDither = true
paintProgress.isAntiAlias = true
paintProgress.strokeWidth = progressStrokeWidth
paintProgress.style = Paint.Style.STROKE
paintProgress.isDither = true
paintText.isAntiAlias = true
paintText.color = textColor
paintText.strokeWidth = 1F
paintText.style = Paint.Style.FILL
paintText.isDither = true
paintNum.isAntiAlias = true
paintNum.strokeWidth = 3f
paintNum.style = Paint.Style.FILL
paintNum.isDither = true
}
/**
* 初始化渐变颜色
*/
private fun initShader() {
rect.set(
(-viewWidth / 2) + OFFSET + paddingLeft, paddingTop - (viewHeight / 2) + OFFSET,
(viewWidth / 2) - paddingRight - OFFSET,
(viewWidth / 2) - paddingBottom - OFFSET
)
val shader = SweepGradient(
0f,
0f,
progressColor,
null
)
val rotate = 90f
val gradientMatrix = Matrix()
gradientMatrix.preRotate(rotate, 0f, 0f)
shader.setLocalMatrix(gradientMatrix)
paintProgress.shader = shader
}
}