自定义控件值九宫格密码锁

思路解析:

1.绘制九个点存在二维数组中,每个点是一个Point对象,有内圆和外圆之分

class Point(var centerX: Int, var centerY: Int, var index: Int) {

    private val STATUS_NORMAL = 1
    private val STATUS_PRESSED = 2
    private val STATUS_ERROR = 3
    // 当前点的状态 有三种状态
    private var status = STATUS_NORMAL


    fun setStatusPressed() {
        status = STATUS_PRESSED
    }

    fun setStatusNormal() {
        status = STATUS_NORMAL
    }

    fun setStatusError() {
        status = STATUS_ERROR
    }

    fun statusIsPressed(): Boolean {
        return status == STATUS_PRESSED
    }

    fun statusIsError(): Boolean {
        return status == STATUS_ERROR
    }

    fun statusIsNormal(): Boolean {
        return status == STATUS_NORMAL
    }
}

2.计算点的位置并且绘制

// 计算和指定点的中心点位置
        mPoints[0][0] = Point(offsetX + squareWidth / 2, offsetY + squareWidth / 2, 0)
        mPoints[0][1] = Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth / 2, 1)
        mPoints[0][2] = Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth / 2, 2)
        mPoints[1][0] = Point(offsetX + squareWidth / 2, offsetY + squareWidth * 3 / 2, 3)
        mPoints[1][1] = Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth * 3 / 2, 4)
        mPoints[1][2] = Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth * 3 / 2, 5)
        mPoints[2][0] = Point(offsetX + squareWidth / 2, offsetY + squareWidth * 5 / 2, 6)
        mPoints[2][1] = Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth * 5 / 2, 7)
        mPoints[2][2] = Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth * 5 / 2, 8)

private fun drawShow(canvas: Canvas) {
    for (i in 0..2) {
        for (point in mPoints[i]) {
            if (point!!.statusIsNormal()) {
                // 先绘制外圆
                mNormalPaint.color = mOuterNormalColor
                canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                        mDotRadius.toFloat(), mNormalPaint)
                // 后绘制内圆
                mNormalPaint.color = mInnerNormalColor
                canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                        mDotRadius / 6.toFloat(), mNormalPaint)
            }

            if (point!!.statusIsPressed()) {
                // 先绘制外圆
                mPressedPaint.color = mOuterPressedColor
                canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                        mDotRadius.toFloat(), mPressedPaint)
                // 后绘制内圆
                mPressedPaint.color = mInnerPressedColor
                canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                        mDotRadius / 6.toFloat(), mPressedPaint)
            }

            if (point!!.statusIsError()) {
                // 先绘制外圆
                mErrorPaint.color = mOuterErrorColor
                canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                        mDotRadius.toFloat(), mErrorPaint)
                // 后绘制内圆
                mErrorPaint.color = mInnerErrorColor
                canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                        mDotRadius / 6.toFloat(), mErrorPaint)
            }
        }
    }

    // 绘制两个点之间的连线以及箭头
    drawLine(canvas)
}

3.绘制点之间的连线,注意需要从小圆的外边开始连线,要计算接触点rx,ry的坐标

 private fun drawLine(start: Point, end: Point, canvas: Canvas, paint: Paint) {
        val pointDistance = MathUtil.distance(start.centerX.toDouble(), start.centerY.toDouble(),
                end.centerX.toDouble(), end.centerY.toDouble())

        var dx = end.centerX - start.centerX
        var dy = end.centerY - start.centerY

        val rx = (dx / pointDistance * (mDotRadius / 6.0)).toFloat()
        val ry = (dy / pointDistance * (mDotRadius / 6.0)).toFloat()
        canvas.drawLine(start.centerX + rx, start.centerY + ry, end.centerX - rx, end.centerY - ry, paint)
    }

4.绘制箭头

 private fun drawArrow(canvas: Canvas, paint: Paint, start: Point, end: Point, arrowHeight: Float, angle: Int) {
        val d = MathUtil.distance(start.centerX.toDouble(), start.centerY.toDouble(), end.centerX.toDouble(), end.centerY.toDouble())
        val sin_B = ((end.centerX - start.centerX) / d).toFloat()
        val cos_B = ((end.centerY - start.centerY) / d).toFloat()
        val tan_A = Math.tan(Math.toRadians(angle.toDouble())).toFloat()
        val h = (d - arrowHeight.toDouble() - mDotRadius * 1.1).toFloat()
        val l = arrowHeight * tan_A
        val a = l * sin_B
        val b = l * cos_B
        val x0 = h * sin_B
        val y0 = h * cos_B
        val x1 = start.centerX + (h + arrowHeight) * sin_B
        val y1 = start.centerY + (h + arrowHeight) * cos_B
        val x2 = start.centerX + x0 - b
        val y2 = start.centerY.toFloat() + y0 + a
        val x3 = start.centerX.toFloat() + x0 + b
        val y3 = start.centerY + y0 - a
        val path = Path()
        path.moveTo(x1, y1)
        path.lineTo(x2, y2)
        path.lineTo(x3, y3)
        path.close()
        canvas.drawPath(path, paint)
    }

5.根据触摸事件改变点的状态

override fun onTouchEvent(event: MotionEvent): Boolean {
        mMovingX = event.x
        mMovingY = event.y

        // 你可以自己用Java ,后面会专门讲 kotlin 的语法
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                // 判断手指是不是按在一个 宫格上面
                // 如何判断一个点在圆里面   点到圆心的距离 < 半径
                var point = point
                if (point != null) {
                    mIsTouchPoint = true
                    mSelectPoints.add(point)
                    // 改变当前点的状态
                    point.setStatusPressed()
                }
            }

            MotionEvent.ACTION_MOVE -> {
                if (mIsTouchPoint) {
                    // 按下的时候一定要在一个点上,不断触摸的时候不断去判断新的点
                    var point = point
                    if (point != null) {
                        if (!mSelectPoints.contains(point)) {
                            mSelectPoints.add(point)
                        }
                        // 改变当前点的状态
                        point.setStatusPressed()
                    }
                }
            }

            MotionEvent.ACTION_UP -> {
                mIsTouchPoint = false
                // 回调密码获取监听 今晚8点再讲  显示错误,错误显示完之后要清空恢复默认
            }
        }

       invalidate()
        return true
    }

具体代码如下:

package com.cmj.myapplication.view

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.cmj.myapplication.MathUtil

/**
 * Email [email protected]
 * Created by Michael Chen on 2018/6/20.
 * Version 1.0
 * Description:九宮格练习
 */
class LockPatternView : View {
    private var mIsInit = false

    // 二维数组初始化,int[3][3]
    private var mPoints: Array<Array<Point?>> = Array(3) { Array<Point?>(3, { null }) }

    // 外圆的半径
    private var mDotRadius = 0

    // 画笔
    private lateinit var mLinePaint: Paint
    private lateinit var mPressedPaint: Paint
    private lateinit var mErrorPaint: Paint
    private lateinit var mNormalPaint: Paint
    private lateinit var mArrowPaint: Paint
    // 颜色
    private val mOuterPressedColor = 0xff8cbad8.toInt()
    private val mInnerPressedColor = 0xff0596f6.toInt()
    private val mOuterNormalColor = 0xffd9d9d9.toInt()
    private val mInnerNormalColor = 0xff929292.toInt()
    private val mOuterErrorColor = 0xff901032.toInt()
    private val mInnerErrorColor = 0xffea0945.toInt()

    // 按下的时候是否是按在一个点上
    private var mIsTouchPoint = false
    // 选中的所有点
    private var mSelectPoints = ArrayList<Point>()

    // 构造函数
    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)


    override fun onDraw(canvas: Canvas) {
        if (!mIsInit){
            initDot()
            initPaint()
            mIsInit=true
        }
        //画九宫格
        drawShow(canvas)
    }

    private fun drawShow(canvas: Canvas) {
        for (i in 0..2) {
            for (point in mPoints[i]) {
                if (point!!.statusIsNormal()) {
                    // 先绘制外圆
                    mNormalPaint.color = mOuterNormalColor
                    canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                            mDotRadius.toFloat(), mNormalPaint)
                    // 后绘制内圆
                    mNormalPaint.color = mInnerNormalColor
                    canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                            mDotRadius / 6.toFloat(), mNormalPaint)
                }

                if (point!!.statusIsPressed()) {
                    // 先绘制外圆
                    mPressedPaint.color = mOuterPressedColor
                    canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                            mDotRadius.toFloat(), mPressedPaint)
                    // 后绘制内圆
                    mPressedPaint.color = mInnerPressedColor
                    canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                            mDotRadius / 6.toFloat(), mPressedPaint)
                }

                if (point!!.statusIsError()) {
                    // 先绘制外圆
                    mErrorPaint.color = mOuterErrorColor
                    canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                            mDotRadius.toFloat(), mErrorPaint)
                    // 后绘制内圆
                    mErrorPaint.color = mInnerErrorColor
                    canvas.drawCircle(point.centerX.toFloat(), point.centerY.toFloat(),
                            mDotRadius / 6.toFloat(), mErrorPaint)
                }
            }
        }

        // 绘制两个点之间的连线以及箭头
        drawLine(canvas)
    }

    private fun drawLine(canvas: Canvas) {
        if (mSelectPoints.size>=1){
            //两个点之间需要绘制一条线和箭头
            var lastPoint = mSelectPoints[0]
            for (index in 1 ..mSelectPoints.size-1){
                //两点之间绘制一条线
                drawLine(lastPoint,mSelectPoints[index],canvas,mLinePaint)
                //两点之间绘制箭头
                drawArrow(canvas,mArrowPaint!!,lastPoint,mSelectPoints[index],(mDotRadius/5).toFloat(),38)
                lastPoint=mSelectPoints[index]
            }
            //绘制最后一个点到手指当前位置连线
            //如果手指在内圆里就不要绘制
            var isInnerPoint = MathUtil.checkInRound(lastPoint.centerX.toFloat(), lastPoint.centerY.toFloat(), mDotRadius.toFloat() / 4
                    , mMovingX, mMovingY)
            if (!isInnerPoint&&mIsTouchPoint){
                drawLine(lastPoint, Point(mMovingX.toInt(), mMovingY.toInt(), -1), canvas, mLinePaint)
            }

        }
    }

    /**
     * 画箭头
     */
    private fun drawArrow(canvas: Canvas, paint: Paint, start: Point, end: Point, arrowHeight: Float, angle: Int) {
        val d = MathUtil.distance(start.centerX.toDouble(), start.centerY.toDouble(), end.centerX.toDouble(), end.centerY.toDouble())
        val sin_B = ((end.centerX - start.centerX) / d).toFloat()
        val cos_B = ((end.centerY - start.centerY) / d).toFloat()
        val tan_A = Math.tan(Math.toRadians(angle.toDouble())).toFloat()
        val h = (d - arrowHeight.toDouble() - mDotRadius * 1.1).toFloat()
        val l = arrowHeight * tan_A
        val a = l * sin_B
        val b = l * cos_B
        val x0 = h * sin_B
        val y0 = h * cos_B
        val x1 = start.centerX + (h + arrowHeight) * sin_B
        val y1 = start.centerY + (h + arrowHeight) * cos_B
        val x2 = start.centerX + x0 - b
        val y2 = start.centerY.toFloat() + y0 + a
        val x3 = start.centerX.toFloat() + x0 + b
        val y3 = start.centerY + y0 - a
        val path = Path()
        path.moveTo(x1, y1)
        path.lineTo(x2, y2)
        path.lineTo(x3, y3)
        path.close()
        canvas.drawPath(path, paint)
    }

    private fun drawLine(start: Point, end: Point, canvas: Canvas, paint: Paint) {
        val pointDistance = MathUtil.distance(start.centerX.toDouble(), start.centerY.toDouble(), end.centerX.toDouble(), end.centerY.toDouble())
        var dx=end.centerX-start.centerX
        var dy=end.centerY-start.centerY
        //小圆上接触点的坐标
        val rx=(dx/pointDistance*(mDotRadius/6.0)).toFloat()
        val ry=(dy/pointDistance*(mDotRadius/6.0)).toFloat()
        canvas.drawLine(start.centerX+rx,start.centerY+ry,end.centerX-rx,end.centerY-ry,paint)
    }

    private val point: Point?
        get() {
            for (i in 0..2) {
                for (point in mPoints[i]) {
                    // for 循环九个点,判断手指位置是否在这个九个点里面
                    if (MathUtil.checkInRound(point!!.centerX.toFloat(), point.centerY.toFloat(),
                                    mDotRadius.toFloat(), mMovingX, mMovingY)) {
                        return point
                    }
                }
            }
            return null
        }
    private var mMovingX = 0f
    private var mMovingY = 0f
    /**
     * 初始化画笔
     * 3个点状态的画笔,线的画笔,箭头的画笔
     */
    private fun initPaint() {
        mLinePaint= Paint()
        mLinePaint.color=mInnerPressedColor
        mLinePaint.style=Paint.Style.STROKE
        mLinePaint.isAntiAlias=true
        mLinePaint.strokeWidth=(mDotRadius/9).toFloat()
        //按下的画笔
        mPressedPaint= Paint()
        mPressedPaint.style=Paint.Style.STROKE
        mPressedPaint.isAntiAlias=true
        mPressedPaint.strokeWidth=(mDotRadius/6).toFloat()
        //错误的画笔
        mErrorPaint= Paint()
        mErrorPaint.style=Paint.Style.STROKE
        mErrorPaint.isAntiAlias=true
        mErrorPaint.strokeWidth=(mDotRadius/6).toFloat()
        //默认画笔
        mNormalPaint= Paint()
        mNormalPaint.style=Paint.Style.STROKE
        mNormalPaint.isAntiAlias=true
        mNormalPaint.strokeWidth=(mDotRadius/6).toFloat()
        //箭头画笔
        mArrowPaint=Paint()
        mArrowPaint.color=mInnerPressedColor
        mArrowPaint.style=Paint.Style.FILL
        mArrowPaint.isAntiAlias=true


    }

    private fun initDot() {
        //九个点存到集合
        //计算中心位置
        var width = this.width
        var height = this.height
        //区分横竖屏
        var offsetX=0
        var offsetY=0
        if (height>width){
            offsetY=(height-width)/2
            height=width
        }else{
            offsetX=(width-height)/2
            width=height
        }
        var squareWidth=width/3
        //外圆的大小根据宽度来决定
        mDotRadius=width/12
        //计算指定点的中心位置
        mPoints[0][0]=Point(offsetX+squareWidth/2,offsetY+squareWidth/2,0)
        mPoints[0][1]=Point(offsetX+squareWidth*3/2,offsetY+squareWidth/2,1)
        mPoints[0][2]=Point(offsetX+squareWidth*5/2,offsetY+squareWidth/2,2)
        mPoints[1][0]=Point(offsetX+squareWidth/2,offsetY+squareWidth*3/2,3)
        mPoints[1][1]=Point(offsetX+squareWidth*3/2,offsetY+squareWidth*3/2,4)
        mPoints[1][2]=Point(offsetX+squareWidth*5/2,offsetY+squareWidth*3/2,5)
        mPoints[2][0]=Point(offsetX+squareWidth/2,offsetY+squareWidth*5/2,6)
        mPoints[2][1]=Point(offsetX+squareWidth*3/2,offsetY+squareWidth*5/2,7)
        mPoints[2][2]=Point(offsetX+squareWidth*5/2,offsetY+squareWidth*5/2,8)



    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        mMovingX=event.x
        mMovingY=event.y
        when(event.action){
            MotionEvent.ACTION_DOWN->{
                //判断手指是否在圆上
                //点到圆心距离小于半径
                var point=point
                if (point!=null){
                    mIsTouchPoint=true
                    mSelectPoints.add(point)
                    //改变当前点的状态
                    point.setStatusPressed()
                }


            }
            MotionEvent.ACTION_MOVE->{
                if (mIsTouchPoint){
                    var point=point
                    if (point!=null){
                        if (!mSelectPoints.contains(point)){
                            mSelectPoints.add(point)
                        }
                        //改变当前状态
                        point.setStatusPressed()
                    }
                }

            }
            MotionEvent.ACTION_UP->{
                mIsTouchPoint=false
            }
        }
        invalidate()
        return true
    }

    /**
     * 九宫格的类
     */
    class Point(var centerX: Int, var centerY: Int, var index: Int) {

        private val STATUS_NORMAL = 1
        private val STATUS_PRESSED = 2
        private val STATUS_ERROR = 3
        // 当前点的状态 有三种状态
        private var status = STATUS_NORMAL


        fun setStatusPressed() {
            status = STATUS_PRESSED
        }

        fun setStatusNormal() {
            status = STATUS_NORMAL
        }

        fun setStatusError() {
            status = STATUS_ERROR
        }

        fun statusIsPressed(): Boolean {
            return status == STATUS_PRESSED
        }

        fun statusIsError(): Boolean {
            return status == STATUS_ERROR
        }

        fun statusIsNormal(): Boolean {
            return status == STATUS_NORMAL
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_34015596/article/details/85038318