自定义控件之空调温度调节控件

手撸一个空调温度调节自定义控件,效果如下
这里写图片描述

这个控件的难点主要是手势控制,其他的都很简单
说下思路吧
* 首先绘制圆盘,刻度,阴影(需要关闭硬件加速),文字
* 然后根据划过的角度绘制进度条
* 最后根绝touch事件重新绘制,并设置数据回调
代码如下

class TempView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
    //画外部的圆的画笔
    private val mOutCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
    //画内部圆的画笔
    private val mInCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
    //画外边缘的线的画笔
    private val mGrayLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
    //画中间文字的画笔
    private val mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    //画进度的画笔1
    private val mLineProgressPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val mProgressCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)

    private val mProgressPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    // 控件宽
    private var mWidth: Int = 0
    // 控件高
    private var mHeight: Int = 0
    // 刻度盘半径
    private var dialRadius: Int = 0
    // 中间圆半径
    private var arcRadius: Int = 0
    private var mBgRectf: RectF? = null
    private var currentAngle: Float = 0f
    // 当前按钮旋转的角度
    private var rotateAngle: Float = 0.toFloat()
    //初始化温度值
    private var temText: Int = 15
    private var isDown: Boolean = false
    private var isMove: Boolean = false
    private var downX: Float = 0f
    private var downY: Float = 0f

    init {
        mOutCirclePaint.color = Color.WHITE
        mOutCirclePaint.style = Paint.Style.STROKE
        mOutCirclePaint.setShadowLayer(15f, 0f, 0f, Color.parseColor("#836FFF"))

        mInCirclePaint.color = Color.WHITE
        mInCirclePaint.strokeWidth = 1f
        mInCirclePaint.style = Paint.Style.FILL
        mInCirclePaint.setShadowLayer(15f, 0f, 0f, Color.parseColor("#836FFF"))


        mGrayLinePaint.color = Color.parseColor("#88836FFF")
        mGrayLinePaint.strokeWidth = dp2px(1f)
        mGrayLinePaint.style = Paint.Style.STROKE


        mTextPaint.textSize = sp2px(32f)
        mTextPaint.style = Paint.Style.FILL
        mTextPaint.color = Color.parseColor("#436EEE")
        mTextPaint.textAlign = Paint.Align.CENTER
        mTextPaint.setFakeBoldText(true)

        mLineProgressPaint.style = Paint.Style.STROKE
        mLineProgressPaint.strokeWidth = dp2px(10f)
        mLineProgressPaint.color = Color.parseColor("#436EEE")
//        mLineProgressPaint.setShadowLayer(15f, 0f, 0f, Color.GRAY)
//        mLineProgressPaint.strokeCap = Paint.Cap.ROUND

        mProgressCirclePaint.style = Paint.Style.FILL


        mProgressPaint.style = Paint.Style.STROKE
        mProgressPaint.strokeWidth = dp2px(8f)
        mProgressPaint.color = Color.parseColor("#436EEE")
        mProgressPaint.strokeCap = Paint.Cap.ROUND
        mProgressPaint.setShadowLayer(15f, 0f, 0f, Color.GRAY)

    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val width = View.MeasureSpec.getSize(widthMeasureSpec)
        val height = View.MeasureSpec.getSize(heightMeasureSpec)
        val imageSize = if (width < height) width else height
        setMeasuredDimension(imageSize, imageSize)

    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mHeight = Math.min(h, w)
        mWidth = height
        dialRadius = (width / 2 - dp2px(10f)).toInt()
        //设置外圆的宽度
        mOutCirclePaint.strokeWidth = dialRadius / 3f

        arcRadius = dialRadius / 2

        mBgRectf = RectF(-dialRadius * 1f / 2 - dp2px(5f), -dialRadius * 1f / 2 - dp2px(5f), dialRadius * 1f / 2 + dp2px(5f), dialRadius * 1f / 2 + dp2px(5f))
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.translate(mWidth * 1f / 2, mHeight * 1f / 2);
        drawBgCircle(canvas)
        drawInCircle(canvas)
        drawLine(canvas)
        drawText(canvas)
        drawProgress(canvas)
        drawPointLine(canvas)
    }

    private val drawPointLine = { canvas: Canvas ->
        canvas.save()
        canvas.rotate(rotateAngle + 2)
        val point = dialRadius * 1f / 2 + dp2px(5f)
        canvas.drawLine(point - dialRadius / 6, 0f, point + dialRadius / 6, 0f, mProgressPaint)
        canvas.restore()
    }

    private val drawProgress = { canvas: Canvas ->
        if (rotateAngle>0){
            mProgressCirclePaint.color =Color.parseColor("#98F5FF")
            canvas.drawCircle(dialRadius*1f/2+dp2px(5f),0f,dp2px(5f),mProgressCirclePaint)
        }
        val colors = intArrayOf(Color.parseColor("#98F5FF"), Color.parseColor("#8470FF"))
        val mShader = SweepGradient(0f, 0f, colors, null)
        mLineProgressPaint.shader = mShader
        canvas.drawArc(mBgRectf, 0f, rotateAngle, false, mLineProgressPaint)
    }

    private val drawText = { canvas: Canvas ->
        val baseLineY = Math.abs(mTextPaint.ascent() + mTextPaint.descent()) / 2
        canvas.drawText("${temText}°", 0f, baseLineY, mTextPaint)
    }

    private val drawLine = { canvas: Canvas ->
        canvas.save()
        var beginAngle: Float = 0f
        for (i in 0..144) {
            canvas.save()
            canvas.rotate(beginAngle)
            canvas.drawLine(dialRadius * 7f / 9, 0f, dialRadius * 8f / 9, 0f, mGrayLinePaint)
            beginAngle += 2.5f
            canvas.restore()
        }
        canvas.restore()
    }

    private val drawInCircle = { canvas: Canvas ->
        canvas.drawCircle(0f, 0f, arcRadius.toFloat(), mInCirclePaint)
    }

    private val drawBgCircle = { canvas: Canvas ->
        canvas.drawCircle(0f, 0f, dialRadius.toFloat() * 5 / 6, mOutCirclePaint)
    }


    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                isDown = true
                downX = event.x
                downY = event.y
                currentAngle = calcAngle(downX, downY)
            }

            MotionEvent.ACTION_MOVE -> {
                isMove = true
                val targetX: Float
                val targetY: Float
                targetX = event.x
                targetY = event.y
                val angle = calcAngle(targetX, targetY)
                // 滑过的角度增量
                var angleIncreased = angle - currentAngle
                if (angleIncreased < -180) {
                    angleIncreased = angleIncreased + 360
                } else if (angleIncreased > 180) {
                    angleIncreased = angleIncreased - 360
                }
                IncreaseAngle(angleIncreased)
                currentAngle = angle
                invalidate()
                // 滑过的角度增量
            }

            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                if (isDown) {
                    if (isMove) {
                        temparetureChangeListner?.invoke(temText)
                        isMove = false
                    }
                    isDown = false
                }
            }

        }
        return true
    }

    /**
     * 增加旋转角度
     *
     * @param angle 增加的角度
     */
    private val IncreaseAngle = { angle: Float ->
        rotateAngle += angle
        if (rotateAngle < 0) {
            rotateAngle = 0f
        } else if (rotateAngle > 360f) {
            rotateAngle = 360f
        }
        temText = (rotateAngle / 360 * 15).toInt() + 15
    }

    /**
     * 以按钮圆心为坐标圆点,建立坐标系,求出(targetX, targetY)坐标与x轴的夹角
     *
     * @param targetX x坐标
     * @param targetY y坐标
     * @return (targetX, targetY)坐标与x轴的夹角
     */
    private val calcAngle = { targetX: Float, targetY: Float ->
        val x = targetX - width / 2
        val y = targetY - height / 2
        val radian: Double

        if (x != 0f) {
            val tan = Math.abs(y / x)
            if (x > 0) {
                if (y >= 0) {
                    radian = Math.atan(tan.toDouble())
                } else {
                    radian = 2 * Math.PI - Math.atan(tan.toDouble())
                }
            } else {
                if (y >= 0) {
                    radian = Math.PI - Math.atan(tan.toDouble())
                } else {
                    radian = Math.PI + Math.atan(tan.toDouble())
                }
            }
        } else {
            if (y > 0) {
                radian = Math.PI / 2
            } else {
                radian = -Math.PI / 2
            }
        }
        (radian * 180 / Math.PI).toFloat()
    }
    var temparetureChangeListner: ((Int) -> Unit)? = null
}

猜你喜欢

转载自blog.csdn.net/villa_mou/article/details/79758118