Touch事件分发 - 九宫格解锁

作者: 红橙Darren | 来源:发表于2017-05-25 11:13 被阅读3206次

    1.概述


    自定义View效果越写越难,但是将这些效果一步一步分解后,其实挺简单的,早期自己项目中用到九宫格解锁,我都是从网上下的,因为心里一开始觉得自己写应该会很困难,后来发现自己闲下来写写原来这么简单。这期的自定义View的效果我们用Kotlin来写

    九宫格解锁

    2.实现


    2.1. 绘制出相对于这个View的居中的九个圆,刚开始当然是默认的
    2.2. 当触摸屏幕的时候判断是否点击在这九个圆上
    2.3. 在屏幕上滑动的时候,绘制两个点之间的线条和箭头,以及选中状态的点
    2.4. 提供几个方法供用户调用,监听回调密码是否太短,合法等等

    2.1. 绘制出相对于这个View的居中的九个圆

    class LockPatternView : View {
    
        // 二维数组初始化,int[3][3]
        private var mPoints: Array<Array<Point?>> = Array(3) { Array<Point?>(3, { null }) }
        // 是否初始化
        private var mIsInit = false
        private var mWidth: Int = 0
        private var mHeight: Int = 0
        // 外圆的半径
        private var mDotRadius: Int = 0
        // 画笔
        private var mLinePaint: Paint? = null
        private var mPressedPaint: Paint? = null
        private var mErrorPaint: Paint? = null
        private var mNormalPaint: Paint? = null
        private var mArrowPaint: Paint? = null
        // 颜色
        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()
    
        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) {
                initPoints()
            }
    
            drawToCanvas(canvas)
        }
    
        private fun drawToCanvas(canvas: Canvas) {
            for (i in mPoints.indices) {
                for (j in 0..mPoints[i].size - 1) {
                    val point = mPoints[i][j]
                    if (point != null) {
                        // 循环绘制默认圆
                        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!!
                    }
                }
            }
    
            drawLineToCanvas(canvas)
        }
    
         /**
         * 初始化点
         */
        private fun initPoints() {
            mWidth = width
            mHeight = height
    
            var offsetX = 0
            var offsetY = 0
    
            if (mWidth > mHeight) {
                offsetX = (mWidth - mHeight) / 2
                mWidth = mHeight
            } else {
                offsetY = (mHeight - mWidth) / 2
                mHeight = mWidth
            }
    
            mDotRadius = mWidth / 12
    
            val padding = mDotRadius / 2
            val sideSize = (mWidth - 2 * padding) / 3
            offsetX += padding
            offsetY += padding
    
            for (i in mPoints.indices) {
                for (j in mPoints.indices) {
                    // 循环初始化九个点
                    mPoints[i][j] = Point(offsetX + sideSize * (i * 2 + 1) / 2,
                            offsetY + sideSize * (j * 2 + 1) / 2, i * mPoints.size + j)
                }
            }
    
            initPaint()
    
            mIsInit = true
        }
    
    
        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 / 9).toFloat()
            // 箭头的画笔
            mArrowPaint = Paint()
            mArrowPaint!!.color = mInnerPressedColor
            mArrowPaint!!.style = Paint.Style.FILL
            mArrowPaint!!.isAntiAlias = true
        }
    
    Kotlin初始化二维数组

    2.2. 当触摸屏幕的时候判断是否点击在这九个圆上

    override fun onTouchEvent(event: MotionEvent): Boolean {
            mMovingX = event.x
            mMovingY = event.y
    
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    val firstPoint = point
                    if (firstPoint != null) {
                        // 已经开始选点了
                        mSelectPoints.add(firstPoint)
                        // 点设置为已经选中
                        firstPoint.setStatusPressed()
                        // 开始绘制
                        mSelectBegin = true
                    }
                }
            }
    
            invalidate()
            return true
        }
    
        /**
         * 获取按下的点
         * @return 当前按下的点
         */
        private val point: Point?
            get() {
                for (i in mPoints.indices) {
                    for (j in 0..mPoints[i].size - 1) {
                        val point = mPoints[i][j]
                        if (point != null) {
                            if (MathUtil.checkInRound(point.centerX.toFloat(), point.centerY.toFloat(), mDotRadius.toFloat(), mMovingX, mMovingY)) {
                                return point
                            }
                        }
                    }
                }
                return null
            }
    

    2.3. 在屏幕上滑动的时候,绘制两个点之间的线条和箭头,以及选中状态的点

    override fun onTouchEvent(event: MotionEvent): Boolean {
            mMovingX = event.x
            mMovingY = event.y
    
            when (event.action) {
                MotionEvent.ACTION_MOVE -> if (mSelectBegin) {
                    val selectPoint = point
                    if (selectPoint != null) {
                        selectPoint.setStatusPressed()
                        if (!mSelectPoints.contains(selectPoint)) {
                            // 把选中的点添加到集合
                            mSelectPoints.add(selectPoint)
                        }
                    }
                }
                MotionEvent.ACTION_UP -> if (mSelectBegin) {
                    if (mSelectPoints.size == 1) {
                        // 清空选择
                        clearSelectPoints()
                    } else if (mSelectPoints.size <= 4) {
                        // 太短显示错误
                        showSelectError()
                    } else {
                        // 成功回调
                        if (mListener != null) {
                            lockCallBack()
                        }
                    }
                    mSelectBegin = false
                }
            }
    
            invalidate()
            return true
        }
    
        /**
         * 画线
         * @param canvas
         */
        private fun drawLineToCanvas(canvas: Canvas) {
            if (mSelectPoints.size >= 1) {
                if (mIsErrorStatus) {
                    mLinePaint!!.color = mInnerErrorColor
                    mArrowPaint!!.color = mInnerErrorColor
                } else {
                    mLinePaint!!.color = mInnerPressedColor
                    mArrowPaint!!.color = mInnerPressedColor
                }
    
                var lastPoint = mSelectPoints[0]
                for (i in 1..mSelectPoints.size - 1) {
                    val point = mSelectPoints[i]
                    // 不断的画线
                    drawLine(lastPoint, point, canvas, mLinePaint!!)
                    drawArrow(canvas, mArrowPaint!!, lastPoint, point, (mDotRadius / 4).toFloat(), 38)
                    lastPoint = point
                }
    
                val isInnerPoint = MathUtil.checkInRound(lastPoint.centerX.toFloat(), lastPoint.centerY.toFloat(), mDotRadius.toFloat(), mMovingX, mMovingY)
                if (mSelectBegin && !isInnerPoint) {
                    drawLine(lastPoint, Point(mMovingX.toInt(), mMovingY.toInt(), -1), canvas, mLinePaint!!)
                }
            }
        }
    
        /**
         * 画线
         */
        private fun drawLine(start: Point, end: Point, canvas: Canvas, paint: Paint) {
            val d = MathUtil.distance(start.centerX.toDouble(), start.centerY.toDouble(), end.centerX.toDouble(), end.centerY.toDouble())
            val rx = (((end.centerX - start.centerX) * mDotRadius).toDouble() / 5.0 / d).toFloat()
            val ry = (((end.centerY - start.centerY) * mDotRadius).toDouble() / 5.0 / d).toFloat()
            canvas.drawLine(start.centerX + rx, start.centerY + ry, end.centerX - rx, end.centerY - ry, paint)
        }
    
        /**
         * 画箭头
         */
        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)
        }
    

    2.4. 提供几个方法供用户调用,监听回调密码是否太短,合法等等

        /**
         * 回调
         */
        private fun lockCallBack() {
            var password = ""
            for (selectPoint in mSelectPoints) {
                password += selectPoint.index
            }
            mListener!!.lock(password)
        }
    
        /**
         * 显示错误
         */
        fun showSelectError() {
            for (selectPoint in mSelectPoints) {
                selectPoint.setStatusError()
                mIsErrorStatus = true
            }
    
            postDelayed({
                clearSelectPoints()
                mIsErrorStatus = false
                invalidate()
            }, 1000)
        }
    
        /**
         * 清空所有的点
         */
        private fun clearSelectPoints() {
            for (selectPoint in mSelectPoints) {
                selectPoint.setStatusNormal()
            }
            mSelectPoints.clear()
        }
    
        /**
         * 清空所有的点
         */
        fun clearSelect() {
            for (selectPoint in mSelectPoints) {
                selectPoint.setStatusNormal()
            }
            mSelectPoints.clear()
            invalidate()
        }
    
        private var mListener: LockPatternListener? = null
        fun setLockPatternListener(listener: LockPatternListener) {
            this.mListener = listener
        }
    
        interface LockPatternListener {
            fun lock(password: String)
        }
    

    如果觉得还是有点困难,可以看看前几篇自定义View的文章,至于箭头的绘制涉及到 sin、cos、tan ,我会在介绍贝塞尔曲线消息拖拽的时候讲一次数学课,当然我们在项目中有可能会直接使用资源图片。

    所有分享大纲:Android进阶之旅 - 自定义View篇

    视频讲解地址:http://pan.baidu.com/s/1pK8eQPt

    相关文章

      网友评论

      本文标题:Touch事件分发 - 九宫格解锁

      本文链接:https://www.haomeiwen.com/subject/txvaxxtx.html