- Calculation of position
In order to properly draw a custom View, you need to know its size, and the View provides a variety of measurement processing methods, most of which do not need to be replaced. If your view does not require special control over its size, you simply replace one method, onSizeChanged(), in which position, size, and any other values related to view size are computed.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
Copy the code
- define
computeXYForSpeed
Compute the x and y axes
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {// Angles are in radians. val startAngle = math.pi * (9/8.0) val Angle = startAngle + pos.ordinal * (math.pi / 4) x = (radius * cos(angle)).toFloat() + width / 2 y = (radius * sin(angle)).toFloat() + height / 2 }Copy the code
- override
onDraw
In the callonDraw
Method must be created firstPaint
object
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}
Copy the code
Sets brush object properties
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
Copy the code
- Draw the background
drawCircle()
//Draw the dial
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
Copy the code
- Draw the small round
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
Copy the code
- Rendering text
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
Copy the code
- complete
onDraw
As follows:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Set dial background color based on the selection.
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSpeedMaxColor
}
// Draw the dial
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
}
Copy the code
DialView.kt
private enum class FanSpeed(val label: Int) {
OFF(R.string.fan_off),
LOW(R.string.fan_low),
MEDIUM(R.string.fan_medium),
HIGH(R.string.fan_high);
fun next(a) = when (this) {
OFF -> LOW
LOW -> MEDIUM
MEDIUM -> HIGH
HIGH -> OFF
}
}
private const val RADIUS_OFFSET_LABEL = 30 //Offset from dial radius to draw text label Specifies the text radius
private const val RADIUS_OFFSET_INDICATOR = -35 //Offset from dial radius to draw indicator
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
// Paint styles used for rendering are initialized here. This
// is a performance optimization, since onDraw() is called
// for every screen refresh.
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0 f
typeface = Typeface.create("", Typeface.BOLD)
}
private var radius = 0.0 f // Radius of the circle
private var fanSpeed = FanSpeed.OFF // The active selection.
//Point at which to draw label and indicator circle position. PointF is a point
//with floating-point coordinates.
private val pointPosition: PointF = PointF(0.0 f.0.0 f)
private val fanSpeedLowColor:Int
private val fanSpeedMediumColor:Int
private val fanSpeedMaxColor:Int
init {
isClickable = true
val typedArray = context.obtainStyledAttributes(attrs,R.styleable.DialView)
fanSpeedLowColor=typedArray.getColor(R.styleable.DialView_fanColor1,0)
fanSpeedMediumColor = typedArray.getColor(R.styleable.DialView_fanColor2,0)
fanSpeedMaxColor = typedArray.getColor(R.styleable.DialView_fanColor3,0)
typedArray.recycle()
updateContentDescription()
// For minsdk >= 21, you can just add a click action. In this app since minSdk is 19,
// you must add a delegate to handle accessibility.
ViewCompat.setAccessibilityDelegate(this.object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
// If the fan speed is OFF, LOW, or MEDIUM, the hint is to change the speed.
// If it is HIGH use reset.
context.getString(if(fanSpeed ! = FanSpeed.HIGH) R.string.changeelse R.string.reset)
)
info.addAction(customClick)
}
})
}
override fun performClick(a): Boolean {
// Give default click listeners priority and perform accessibility/autofill events.
// Also calls onClickListener() to handle further subclass customizations.
if (super.performClick()) return true
// Rotates between each of the different selection
// states on each click.
fanSpeed = fanSpeed.next()
updateContentDescription()
// Redraw the view.
invalidate()
return true
}
/**
* This is called during layout when the size of this view has changed. If
* the view was just added to the view hierarchy, it is called with the old
* values of 0. The code determines the drawing bounds for the custom view.
*
* @param width Current width of this view.
* @param height Current height of this view.
* @param oldWidth Old width of this view.
* @param oldHeight Old height of this view.
*/
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
// Calculate the radius from the smaller of the width and height.
radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
/**
* Renders view content: an outer circle to serve as the "dial",
* and a smaller black circle to server as the indicator.
* The position of the indicator is based on fanSpeed.
*
* @param canvas The canvas on which the background will be drawn.
*/
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Set dial background color based on the selection.
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSpeedMaxColor
}
// Draw the dial
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
}
/**
* Computes the X/Y-coordinates for a label or indicator,
* given the FanSpeed and radius where the label should be drawn.
*
* @param pos Position (FanSpeed)
* @param radius Radius where label/indicator is to be drawn.
* @return 2-element array. Element 0 is X-coordinate, element 1 is Y-coordinate.
*/
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
// Angles are in radians.
val startAngle = Math.PI * (9 / 8.0)
val angle = startAngle + pos.ordinal * (Math.PI / 4)
x = (radius * cos(angle)).toFloat() + width / 2
y = (radius * sin(angle)).toFloat() + height / 2
}
/** * Updates the view's content description with the appropirate string for the * current fan speed. */
private fun updateContentDescription(a) {
contentDescription = resources.getString(fanSpeed.label)
}
}
Copy the code
- Interface is introduced into
<com.example.android.customfancontroller.DialView
android:id="@+id/dialView"
android:layout_width="@dimen/fan_dimen"
android:layout_height="@dimen/fan_dimen"
app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="# 009688"/>
Copy the code
- atts
<? The XML version = "1.0" encoding = "utf-8"? > <resources> <declare-styleable name="DialView"> <attr name="fanColor1" format="color" /> <attr name="fanColor2" format="color" /> <attr name="fanColor3" format="color" /> </declare-styleable> </resources>Copy the code