1
+ package com.hfad.fancontroller
2
+
3
+ import android.content.Context
4
+ import android.graphics.*
5
+ import android.util.AttributeSet
6
+ import android.view.View
7
+ import android.view.accessibility.AccessibilityNodeInfo
8
+ import androidx.core.content.withStyledAttributes
9
+ import androidx.core.view.AccessibilityDelegateCompat
10
+ import androidx.core.view.ViewCompat
11
+ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
12
+ import kotlin.math.cos
13
+ import kotlin.math.min
14
+ import kotlin.math.sin
15
+
16
+ private enum class FanSpeed (val label : Int ) {
17
+ OFF (R .string.fan_off),
18
+ LOW (R .string.fan_low),
19
+ MEDIUM (R .string.fan_medium),
20
+ HIGH (R .string.fan_high);
21
+
22
+ fun next () = when (this ) {
23
+ OFF -> LOW
24
+ LOW -> MEDIUM
25
+ MEDIUM -> HIGH
26
+ HIGH -> OFF
27
+ }
28
+ }
29
+ private const val RADIUS_OFFSET_LABEL = 30
30
+ private const val RADIUS_OFFSET_INDICATOR = - 35
31
+
32
+ class DialView @JvmOverloads constructor(
33
+ context : Context , attrs : AttributeSet ? = null , defStyleAttr : Int = 0
34
+ ) : View(context, attrs, defStyleAttr) {
35
+ private var radius = 0.0f
36
+ private var fanSpeed = FanSpeed .OFF
37
+ private val pointPosition: PointF = PointF (0.0f , 0.0f )
38
+ private val paint = Paint (Paint .ANTI_ALIAS_FLAG ).apply {
39
+ style = Paint .Style .FILL
40
+ textAlign = Paint .Align .CENTER
41
+ textSize = 55.0f
42
+ typeface = Typeface .create(" " , Typeface .BOLD )
43
+ }
44
+ // custom attributes
45
+ private var fanSpeedLowColor = 0
46
+ private var fanSpeedMediumColor = 0
47
+ private var fanSpeedMaxColor = 0
48
+
49
+ init {
50
+ isClickable = true
51
+ context.withStyledAttributes(attrs, R .styleable.DialView ) {
52
+ fanSpeedLowColor = getColor(R .styleable.DialView_fanColor1 , 0 )
53
+ fanSpeedMediumColor = getColor(R .styleable.DialView_fanColor2 , 0 )
54
+ fanSpeedMaxColor = getColor(R .styleable.DialView_fanColor3 , 0 )
55
+ }
56
+ updateContentDescription()
57
+
58
+ ViewCompat .setAccessibilityDelegate(this , object : AccessibilityDelegateCompat () {
59
+ override fun onInitializeAccessibilityNodeInfo (host : View ,
60
+ info : AccessibilityNodeInfoCompat ) {
61
+ super .onInitializeAccessibilityNodeInfo(host, info)
62
+ val customClick = AccessibilityNodeInfoCompat .AccessibilityActionCompat (
63
+ AccessibilityNodeInfo .ACTION_CLICK ,
64
+ context.getString(if (fanSpeed != FanSpeed .HIGH )
65
+ R .string.change else R .string.reset)
66
+ )
67
+ info.addAction(customClick)
68
+ }
69
+ })
70
+ }
71
+
72
+ override fun performClick (): Boolean {
73
+ if (super .performClick()) return true // calls onClickListener()
74
+ fanSpeed = fanSpeed.next()
75
+ contentDescription = resources.getString(fanSpeed.label)
76
+ updateContentDescription()
77
+ invalidate() // redraw
78
+ return true
79
+ }
80
+
81
+ override fun onSizeChanged (w : Int , h : Int , oldw : Int , oldh : Int ) {
82
+ radius = (min(w, h) / 2.0 * 0.8 ).toFloat()
83
+ super .onSizeChanged(w, h, oldw, oldh)
84
+ }
85
+
86
+ override fun onDraw (canvas : Canvas ) {
87
+ super .onDraw(canvas)
88
+ // paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
89
+ paint.color = when (fanSpeed) {
90
+ FanSpeed .OFF -> Color .GRAY
91
+ FanSpeed .LOW -> fanSpeedLowColor
92
+ FanSpeed .MEDIUM -> fanSpeedMediumColor
93
+ FanSpeed .HIGH -> fanSpeedMaxColor
94
+ }
95
+ // Draw the dial
96
+ canvas.drawCircle((width / 2 ).toFloat(), (height / 2 ).toFloat(), radius, paint)
97
+ // Draw the indicator circle
98
+ val markerRadius = radius + RADIUS_OFFSET_INDICATOR
99
+ pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
100
+ paint.color = Color .BLACK
101
+ canvas.drawCircle(pointPosition.x, pointPosition.y, radius/ 12 , paint)
102
+ // Draw the text labels
103
+ val labelRadius = radius + RADIUS_OFFSET_LABEL
104
+ for (i in FanSpeed .values()) {
105
+ pointPosition.computeXYForSpeed(i, labelRadius)
106
+ val label = resources.getString(i.label)
107
+ canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
108
+ }
109
+ }
110
+
111
+ private fun PointF.computeXYForSpeed (pos : FanSpeed , radius : Float ) {
112
+ val startAngle = Math .PI * (9 / 8.0 )
113
+ val angle = startAngle + pos.ordinal * (Math .PI / 4 )
114
+ x = (radius * cos(angle)).toFloat() + width / 2
115
+ y = (radius * sin(angle)).toFloat() + height / 2
116
+ }
117
+
118
+ fun updateContentDescription () {
119
+ contentDescription = resources.getString(fanSpeed.label)
120
+ }
121
+ }
0 commit comments