Newer
Older
Endoscope / app / src / main / java / com / casic / endoscope / widgets / DirectionControlView.kt
package com.casic.endoscope.widgets

import android.content.Context
import android.graphics.BlurMaskFilter
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Rect
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import com.casic.endoscope.R
import com.pengxh.kt.lite.extensions.dp2px
import kotlin.math.acos
import kotlin.math.cos
import kotlin.math.hypot
import kotlin.math.pow
import kotlin.math.sin
import kotlin.math.sqrt

class DirectionControlView constructor(context: Context, attrs: AttributeSet) :
    View(context, attrs), View.OnTouchListener {

    private val kTag = "DirectionControlView"
    private val maskFilter = BlurMaskFilter(15f, BlurMaskFilter.Blur.SOLID)

    //View中心X坐标
    private var centerX = 0f

    //View中心Y坐标
    private var centerY = 0f

    private lateinit var tickPaint: Paint
    private lateinit var outerPaint: Paint
    private lateinit var innerPaint: Paint

    //View属性
    private val circleRadius: Int
    private val outerColor: Int
    private val outerStroke: Int
    private val innerRadius: Int
    private val innerColor: Int
    private val innerStroke: Int
    private val renderColor: Int

    //滑动圆的圆心
    private var moveCircleX = 0f
    private var moveCircleY = 0f

    //控件外边界
    private val viewRadius: Int
    private val rect: Rect

    private val maxMoveRadius: Int
    private lateinit var directionPaints: ArrayList<Paint>
    private lateinit var leftPath: Path
    private lateinit var topPath: Path
    private lateinit var rightPath: Path
    private lateinit var bottomPath: Path

    // 各方位状态
    private var leftTurn = false
    private var topTurn = false
    private var rightTurn = false
    private var bottomTurn = false

    init {
        val attr = context.obtainStyledAttributes(attrs, R.styleable.DirectionControlView)
        circleRadius = attr.getDimensionPixelOffset(
            R.styleable.DirectionControlView_ctrl_radius, 150
        )
        //中心圆半径
        innerRadius = circleRadius / 3

        outerColor = attr.getColor(R.styleable.DirectionControlView_ctrl_outer_color, Color.LTGRAY)
        outerStroke = attr.getDimensionPixelOffset(
            R.styleable.DirectionControlView_ctrl_outer_stroke, 3
        )
        innerColor = attr.getColor(R.styleable.DirectionControlView_ctrl_inner_color, Color.WHITE)
        innerStroke = attr.getDimensionPixelOffset(
            R.styleable.DirectionControlView_ctrl_inner_stroke, 3
        )
        renderColor = attr.getColor(R.styleable.DirectionControlView_ctrl_render_color, Color.BLUE)
        attr.recycle()

        //辅助框
        viewRadius = circleRadius + 5.dp2px(context)
        rect = Rect(-viewRadius, -viewRadius, viewRadius, viewRadius)

        //滑动圆心最大距离
        maxMoveRadius = innerRadius * 2

        //初始化画笔
        initPaint()

        //事件监听
        setOnTouchListener(this)
    }

    private fun initPaint() {
        tickPaint = Paint()
        tickPaint.color = Color.DKGRAY
        tickPaint.style = Paint.Style.STROKE
        tickPaint.strokeWidth = 1f
        tickPaint.isAntiAlias = true

        outerPaint = Paint()
        outerPaint.color = outerColor
        outerPaint.style = Paint.Style.STROKE
        outerPaint.strokeWidth = outerStroke.toFloat()
        outerPaint.isAntiAlias = true

        innerPaint = Paint()
        innerPaint.color = innerColor
        innerPaint.style = Paint.Style.FILL
        innerPaint.isAntiAlias = true
        //设置光晕
        innerPaint.maskFilter = maskFilter

        /**
         * 左上右下
         * */
        directionPaints = createDirectionPaint()

        //箭头顶点距离圆心的距离
        val distance = maxMoveRadius * 1.35f
        //箭头每边长
        val l = innerRadius * 0.25f
        //箭头顶点与圆心的连线,箭头边长之间的夹角
        val cos = cos(Math.PI / 4).toFloat()
        val sin = sin(Math.PI / 4).toFloat()

        leftPath = Path()
        leftPath.moveTo(-distance, 0f)
        leftPath.lineTo(-distance + l * cos, -l * sin)
        leftPath.moveTo(-distance, 0f)
        leftPath.lineTo(-distance + l * cos, l * sin)

        topPath = Path()
        topPath.moveTo(0f, -distance)
        topPath.lineTo(-l * cos, -distance + l * sin)
        topPath.moveTo(0f, -distance)
        topPath.lineTo(l * cos, -distance + l * sin)

        rightPath = Path()
        rightPath.moveTo(distance, 0f)
        rightPath.lineTo(distance - l * cos, -l * sin)
        rightPath.moveTo(distance, 0f)
        rightPath.lineTo(distance - l * cos, l * sin)

        bottomPath = Path()
        bottomPath.moveTo(0f, distance)
        bottomPath.lineTo(-l * cos, distance - l * sin)
        bottomPath.moveTo(0f, distance)
        bottomPath.lineTo(l * cos, distance - l * sin)
    }

    //批量创建画笔
    private fun createDirectionPaint(): ArrayList<Paint> {
        val result = ArrayList<Paint>()
        for (i in 0 until 4) {
            val paint = Paint()
            paint.color = innerColor
            paint.style = Paint.Style.STROKE
            paint.strokeCap = Paint.Cap.ROUND
            paint.strokeWidth = innerStroke.toFloat()
            paint.isAntiAlias = true
            //设置光晕
            paint.maskFilter = maskFilter

            result.add(paint)
        }
        return result
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        centerX = (w shr 1).toFloat()
        centerY = (h shr 1).toFloat()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec)
        // 获取宽
        val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) {
            // match_parent/精确值
            widthSpecSize
        } else {
            // wrap_content,外边界宽
            (viewRadius * 2)
        }
        // 获取高
        val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) {
            // match_parent/精确值
            heightSpecSize
        } else {
            // wrap_content,外边界高
            (viewRadius * 2)
        }
        // 设置该view的宽高
        setMeasuredDimension(mWidth, mHeight)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        /**
         * 画布移到中心位置,方便绘制一系列图形
         */
        canvas.translate(centerX, centerY)

//        drawGuides(canvas)

        //画外圆
        canvas.drawCircle(0f, 0f, circleRadius.toFloat(), outerPaint)

        //画内圆
        canvas.drawCircle(moveCircleX, moveCircleY, innerRadius.toFloat(), innerPaint)

        //画上下左右箭头
        canvas.drawPath(leftPath, directionPaints[0])
        canvas.drawPath(topPath, directionPaints[1])
        canvas.drawPath(rightPath, directionPaints[2])
        canvas.drawPath(bottomPath, directionPaints[3])
    }

    override fun onTouch(v: View, event: MotionEvent): Boolean {
        val action = event.action
        val x = event.x
        val y = event.y
        when (action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(kTag, "onTouch => 按下")
            }

            MotionEvent.ACTION_MOVE -> {
                /**
                 * 计算可滑动圆的圆心位置
                 * */
                var deltaX = x - circleRadius
                val deltaY = y - circleRadius

                //计算滑动的圆心距离圆心的距离。hypot 表示平方和的开平方
                var distance = hypot(deltaX, deltaY)
                if (distance >= maxMoveRadius) {
                    distance = maxMoveRadius.toFloat()
                }

                if (deltaX >= maxMoveRadius) {
                    deltaX = maxMoveRadius - 0.00001f
                } else if (deltaX < -maxMoveRadius) {
                    deltaX = -maxMoveRadius + 0.00001f
                }

                moveCircleX = deltaX
                val tempY = distance.pow(2) - deltaX.pow(2)
                moveCircleY = if (deltaY < 0) {
                    -sqrt(tempY)
                } else {
                    sqrt(tempY)
                }

                if (distance >= innerRadius) {
                    Log.d(kTag, "onTouch => $distance, $deltaX, $tempY, $moveCircleY")
                    val arcCos = acos(deltaX / distance)
                    val degree = Math.toDegrees(arcCos.toDouble())
                    if (degree in 45.0..135.0) {
                        if (moveCircleY < 0) {
                            updateDirectionColor(ControlDirection.TOP)
                        } else {
                            updateDirectionColor(ControlDirection.BOTTOM)
                        }
                    } else {
                        if (moveCircleX < 0) {
                            updateDirectionColor(ControlDirection.LEFT)
                        } else {
                            updateDirectionColor(ControlDirection.RIGHT)
                        }
                    }
                } else {
                    Log.d(kTag, "onTouch => 点击了中心")
                }
            }

            MotionEvent.ACTION_UP -> {
                moveCircleX = 0f
                moveCircleY = 0f

                directionPaints.forEach { it.color = Color.WHITE }
            }
        }
        invalidate()
        return true
    }

    private fun updateDirectionColor(direction: ControlDirection) {
        when (direction) {
            ControlDirection.LEFT -> {
                directionPaints[0].color = renderColor
                for (i in 1 until directionPaints.size) {
                    directionPaints[i].color = Color.WHITE
                }
            }

            ControlDirection.TOP -> {
                directionPaints[0].color = Color.WHITE
                directionPaints[1].color = renderColor
                directionPaints[2].color = Color.WHITE
                directionPaints[3].color = Color.WHITE
            }

            ControlDirection.RIGHT -> {
                directionPaints[0].color = Color.WHITE
                directionPaints[1].color = Color.WHITE
                directionPaints[2].color = renderColor
                directionPaints[3].color = Color.WHITE
            }

            ControlDirection.BOTTOM -> {
                for (i in 0 until directionPaints.size - 1) {
                    directionPaints[i].color = Color.WHITE
                }
                directionPaints[3].color = renderColor
            }
        }
    }

    /**
     * 辅助线
     * */
    private fun drawGuides(canvas: Canvas) {
        //最外层方框,即自定义View的边界
        canvas.drawRect(rect, tickPaint)

        //触摸圆可到达的最大圆
        canvas.drawCircle(0f, 0f, (innerRadius * 2).toFloat(), tickPaint)

        //中心横线
        canvas.drawLine(-viewRadius.toFloat(), 0f, viewRadius.toFloat(), 0f, tickPaint)

        //中心竖线
        canvas.drawLine(0f, -viewRadius.toFloat(), 0f, viewRadius.toFloat(), tickPaint)

        //对角线
        canvas.drawLine(
            -viewRadius.toFloat(), -viewRadius.toFloat(),
            viewRadius.toFloat(), viewRadius.toFloat(),
            tickPaint
        )

        canvas.drawLine(
            -viewRadius.toFloat(), viewRadius.toFloat(),
            viewRadius.toFloat(), -viewRadius.toFloat(),
            tickPaint
        )
    }

    enum class ControlDirection {
        LEFT, TOP, RIGHT, BOTTOM
    }

    private var listener: OnWheelTouchListener? = null

    fun setOnWheelTouchListener(listener: OnWheelTouchListener?) {
        this.listener = listener
    }

    interface OnWheelTouchListener {
        /**
         * 中间
         */
        fun onCenterClicked()

        /**
         * 左
         */
        fun onLeftTurn()

        /**
         * 上
         */
        fun onTopTurn()

        /**
         * 右
         */
        fun onRightTurn()

        /**
         * 下
         */
        fun onBottomTurn()

        /**
         * 松开
         */
        fun onActionTurnUp(dir: ControlDirection)
    }
}