• 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
  • definecomputeXYForSpeedCompute 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 onDrawIn the callonDrawMethod must be created firstPaintobject
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
  • completeonDrawAs 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