Android 手势锁 (Kotlin)

18/04/17 补充

某些情况下会触发边界条件,导致 ondraw方法根本不运行,csdn界面修改完毕, git修改后期补上

----------------------------------------------------------------

参考的鸿神代码,做的修改 ,Kotlin正在学习,做个记录

目标: 实现设置密码功能

手势锁 绘制完毕后,显示绘制图形,改为 绘制完毕后,自动消除图形

绘制后监听绘制密码是否同存入密码一致,改为监听获得绘制密码结果

原文地址:http://blog.csdn.net/lmj623565791/article/details/36236113

修改后代码:

attrs

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="color_no_finger_inner_circle" format="color" />
    <attr name="color_no_finger_outer_circle" format="color" />
    <attr name="color_finger_on" format="color" />
    <attr name="color_finger_up" format="color" />
    <attr name="count" format="integer" />
    <attr name="tryTimes" format="integer" />

    <declare-styleable name="GestureLockViewGroup">
        <attr name="color_no_finger_inner_circle" />
        <attr name="color_no_finger_outer_circle" />
        <attr name="color_finger_on" />
        <attr name="color_finger_up" />
        <attr name="count" />
        <attr name="tryTimes" />
    </declare-styleable>

</resources>

GestureLockView

import java.util.ArrayList

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Point
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.RelativeLayout
import com.qingxian.finance.R
import com.qingxian.finance.extention.logI

import com.qingxian.finance.ui.login.GestureLockView.Mode

/**
 * Created by minlewan on 2018/1/19.
 */

class GestureLockViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet,
                                                     defStyle: Int = 0) : RelativeLayout(context, attrs, defStyle) {
    /**
     * 保存所有的GestureLockView
     */
    private var mGestureLockViews: Array<GestureLockView?>? = null
    /**
     * 每个边上的GestureLockView的个数
     */
    private var mCount = 4
    /**
     * 存储答案
     */
    private var mAnswer = intArrayOf(0, 1, 2, 5, 8)
    /**
     * 保存用户选中的GestureLockViewid
     */
    private val mChoose = ArrayList<Int>()

    private val mPaint: Paint
    /**
     * 每个GestureLockView中间的间距 设置为:mGestureLockViewWidth * 25%
     */
    private var mMarginBetweenLockView = 30
    /**
     * GestureLockView的边长 4 * mWidth / ( 5 * mCount + 1 )
     */
    private var mGestureLockViewWidth: Int = 0

    /**
     * GestureLockView无手指触摸的状态下内圆的颜色
     */
    private var mNoFingerInnerCircleColor = -0x6c6f70
    /**
     * GestureLockView无手指触摸的状态下外圆的颜色
     */
    private var mNoFingerOuterCircleColor = -0x1f2425
    /**
     * GestureLockView手指触摸的状态下内圆和外圆的颜色
     */
    private var mFingerOnColor = -0xc87037
    /**
     * GestureLockView手指抬起的状态下内圆和外圆的颜色
     */
    private var mFingerUpColor = -0x10000

    /**
     * 宽度
     */
    private var mWidth: Int = 0
    /**
     * 高度
     */
    private var mHeight: Int = 0

    private val mPath: Path?
    /**
     * 指引线的开始位置x
     */
    private var mLastPathX: Int = 0
    /**
     * 指引线的开始位置y
     */
    private var mLastPathY: Int = 0
    /**
     * 指引下的结束位置
     */
    private val mTmpTarget = Point()

    /**
     * 最大尝试次数
     */
    private var mTryTimes = 4
    /**
     * 回调接口
     */
    private var mOnGestureLockViewListener: OnGestureLockViewListener? = null

    init {
        /**
         * 获得所有自定义的参数的值
         */
        val a = context.theme.obtainStyledAttributes(attrs,
                R.styleable.GestureLockViewGroup, defStyle, 0)
        val n = a.indexCount

        for (i in 0 until n) {
            val attr = a.getIndex(i)
            when (attr) {
                R.styleable.GestureLockViewGroup_color_no_finger_inner_circle -> mNoFingerInnerCircleColor = a.getColor(attr,
                        mNoFingerInnerCircleColor)
                R.styleable.GestureLockViewGroup_color_no_finger_outer_circle -> mNoFingerOuterCircleColor = a.getColor(attr,
                        mNoFingerOuterCircleColor)
                R.styleable.GestureLockViewGroup_color_finger_on -> mFingerOnColor = a.getColor(attr, mFingerOnColor)
                R.styleable.GestureLockViewGroup_color_finger_up -> mFingerUpColor = a.getColor(attr, mFingerUpColor)
                R.styleable.GestureLockViewGroup_count -> mCount = a.getInt(attr, 3)
                R.styleable.GestureLockViewGroup_tryTimes -> mTryTimes = a.getInt(attr, 5)
                else -> {
                }
            }
        }

        a.recycle()

        // 初始化画笔
        mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mPaint.style = Paint.Style.STROKE
        // mPaint.setStrokeWidth(20);
        mPaint.strokeCap = Paint.Cap.ROUND
        mPaint.strokeJoin = Paint.Join.ROUND
        // mPaint.setColor(Color.parseColor("#aaffffff"));
        mPath = Path()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        this.setWillNotDraw(false)
        mWidth = View.MeasureSpec.getSize(widthMeasureSpec)
        mHeight = View.MeasureSpec.getSize(heightMeasureSpec)
        mWidth = if (mWidth < mHeight) mWidth else mHeight
        mHeight = mWidth
        // setMeasuredDimension(mWidth, mHeight);
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        // 初始化mGestureLockViews
        if (mGestureLockViews == null) {
            mGestureLockViews = arrayOfNulls(mCount * mCount)
            // 计算每个GestureLockView的宽度
            mGestureLockViewWidth = (4f * mWidth.toFloat() * 1.0f / (5 * mCount + 1)).toInt()
            //计算每个GestureLockView的间距
            mMarginBetweenLockView = (mGestureLockViewWidth * 0.25).toInt()
            // 设置画笔的宽度为GestureLockView的内圆直径稍微小点(不喜欢的话,随便设)
            mPaint.strokeWidth = mGestureLockViewWidth * 0.25f
            for (i in mGestureLockViews!!.indices) {
                //初始化每个GestureLockView
                mGestureLockViews!![i] = GestureLockView(context,
                        mNoFingerInnerCircleColor, mNoFingerOuterCircleColor,
                        mFingerOnColor, mFingerUpColor)
                mGestureLockViews!![i]!!.id = i + 1
                //设置参数,主要是定位GestureLockView间的位置
                val lockerParams = RelativeLayout.LayoutParams(
                        mGestureLockViewWidth, mGestureLockViewWidth)
                // 不是每行的第一个,则设置位置为前一个的右边
                if (i % mCount != 0) {
                    lockerParams.addRule(RelativeLayout.RIGHT_OF,
                            mGestureLockViews!![i - 1]!!.id)
                }
                // 从第二行开始,设置为上一行同一位置View的下面
                if (i > mCount - 1) {
                    lockerParams.addRule(RelativeLayout.BELOW,
                            mGestureLockViews!![i - mCount]!!.id)
                }
                //设置右下左上的边距
                val rightMargin = mMarginBetweenLockView
                val bottomMargin = mMarginBetweenLockView
                var leftMagin = 0
                var topMargin = 0
                /**
                 * 每个View都有右外边距和底外边距 第一行的有上外边距 第一列的有左外边距
                 */
                if (i in 0..(mCount - 1))
                // 第一行
                {
                    topMargin = mMarginBetweenLockView
                }
                if (i % mCount == 0)
                // 第一列
                {
                    leftMagin = mMarginBetweenLockView
                }

                lockerParams.setMargins(leftMagin, topMargin, rightMargin,
                        bottomMargin)
                mGestureLockViews!![i]!!.setMode(Mode.STATUS_NO_FINGER)
                addView(mGestureLockViews!![i], lockerParams)
            }
        }
    }

    override fun performClick(): Boolean {
        logI("performClick: doNothing")
        return super.performClick()
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val action = event.action
        val x = event.x.toInt()
        val y = event.y.toInt()
        when (action) {
            MotionEvent.ACTION_DOWN -> {
                performClick()
                reset()
            }
            MotionEvent.ACTION_MOVE -> {
                mPaint.color = mFingerOnColor
                mPaint.alpha = 50
                val child = getChildIdByPos(x, y)
                if (child != null) {
                    val cId = child.id
                    if (!mChoose.contains(cId)) {
                        mChoose.add(cId)
                        child.setMode(Mode.STATUS_FINGER_ON)
                        if (mOnGestureLockViewListener != null)
                            mOnGestureLockViewListener!!.onBlockSelected(cId)
                        // 设置指引线的起点
                        mLastPathX = child.left / 2 + child.right / 2
                        mLastPathY = child.top / 2 + child.bottom / 2

                        if (mChoose.size == 1)
                        // 当前添加为第一个
                        {
                            mPath!!.moveTo(mLastPathX.toFloat(), mLastPathY.toFloat())
                        } else
                        // 非第一个,将两者使用线连上
                        {
                            mPath!!.lineTo(mLastPathX.toFloat(), mLastPathY.toFloat())
                        }

                    }
                }
                // 指引线的终点
                mTmpTarget.x = x
                mTmpTarget.y = y
            }
            MotionEvent.ACTION_UP -> {

                mPaint.color = mFingerUpColor
                mPaint.alpha = 50
                this.mTryTimes--

                // 回调是否成功
                if (mOnGestureLockViewListener != null && mChoose.size > 0) {
                    mOnGestureLockViewListener!!.onGestureEvent(getmChoose())// 手离开后,调用接口 判断是否正确
                    if (this.mTryTimes == 0)
                    // 如果用完了最后一次
                    {
                        mOnGestureLockViewListener!!.onUnmatchedExceedBoundary()// 手离开后,调用接口 做出相应操作
                    }
                }
                // 将终点设置位置为起点,即取消指引线
                mTmpTarget.x = mLastPathX
                mTmpTarget.y = mLastPathY
            }
        }
        invalidate()
        return true
    }

    private fun changeItemMode() {
        for (gestureLockView in mGestureLockViews!!) {
            if (mChoose.contains(gestureLockView!!.id)) {
                gestureLockView.setMode(Mode.STATUS_FINGER_UP)
            }
        }
    }

    /**
     *
     * 做一些必要的重置
     */
    fun reset() {
        mChoose.clear()
        mPath!!.reset()
        for (gestureLockView in mGestureLockViews!!) {
            gestureLockView!!.setMode(Mode.STATUS_NO_FINGER)
            gestureLockView.arrowDegree = -1
        }
    }

    /**
     * 检查用户绘制的手势是否正确
     * @return
     */
    fun checkAnswer(): Boolean {
        if (mAnswer.size != mChoose.size)
            return false

        for (i in mAnswer.indices) {
            if (mAnswer[i] != mChoose[i])
                return false
        }

        return true
    }

    /**
     * 检查当前左边是否在child     * @param child
     * @param x
     * @param y
     * @return
     */
    private fun checkPositionInChild(child: View, x: Int, y: Int): Boolean {

        //设置了内边距,即x,y必须落入下GestureLockView的内部中间的小区域中,可以通过调整padding使得x,y落入范围不变大,或者不设置padding
        val padding = (mGestureLockViewWidth * 0.15).toInt()

        return (x >= child.left + padding && x <= child.right - padding
                && y >= child.top + padding
                && y <= child.bottom - padding)
    }

    /**
     * 通过x,y获得落入的GestureLockView
     * @param x
     * @param y
     * @return
     */
    private fun getChildIdByPos(x: Int, y: Int): GestureLockView? {
        for (gestureLockView in mGestureLockViews!!) {
            if (checkPositionInChild(gestureLockView!!, x, y)) {
                return gestureLockView
            }
        }

        return null

    }

    /**
     * 获得mChoose也就是选择的密码集合
     * @return
     */
    private fun getmChoose(): List<Int> {
        return mChoose
    }

    /**
     * 设置回调接口
     *
     * @param listener
     */
    fun setOnGestureLockViewListener(listener: OnGestureLockViewListener) {
        this.mOnGestureLockViewListener = listener
    }

    /**
     * 对外公布设置答案的方法
     *
     * @param answer
     */
    fun setAnswer(answer: IntArray) {
        this.mAnswer = answer
    }

    /**
     * 设置最大实验次数
     *
     * @param boundary
     */
    fun setUnMatchExceedBoundary(boundary: Int) {
        this.mTryTimes = boundary
    }

    public override fun dispatchDraw(canvas: Canvas) {
        super.dispatchDraw(canvas)
        //绘制GestureLockView间的连线
        if (mPath != null) {
            canvas.drawPath(mPath, mPaint)
        }
        //绘制指引线
        if (mChoose.size > 0) {
            if (mLastPathX != 0 && mLastPathY != 0)
                canvas.drawLine(mLastPathX.toFloat(), mLastPathY.toFloat(), mTmpTarget.x.toFloat(),
                        mTmpTarget.y.toFloat(), mPaint)
        }

    }

    interface OnGestureLockViewListener {
        /**
         * 单独选中元素的Id
         *
         * @param cId
         */
        fun onBlockSelected(cId: Int)

        /**
         * 是否匹配
         *
         * @param mChoose
         */
        fun onGestureEvent(mChoose: List<Int>)

        /**
         * 超过尝试次数
         */
        fun onUnmatchedExceedBoundary()
    }

    companion object {

        private val TAG = "GestureLockViewGroup"
    }
}
 GesturelockView


 
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Paint.Style
import android.graphics.Path
import android.util.Log
import android.view.View

/**
 * Created by minlewan on 2018/1/19.
 */

class GestureLockView(context: Context,
                      /**
                       * 四个颜色,可由用户自定义,初始化时由GestureLockViewGroup传入
                       */
                      private val mColorNoFingerInner: Int, private val mColorNoFingerOuter: Int, private val mColorFingerOn: Int, private val mColorFingerUp: Int) : View(context, null) {

    /**
     * GestureLockView的当前状态
     */
    private var mCurrentStatus = Mode.STATUS_NO_FINGER

    /**
     * 宽度
     */
    private var mWidth: Int = 0
    /**
     * 高度
     */
    private var mHeight: Int = 0
    /**
     * 外圆半径
     */
    private var mRadius: Int = 0
    /**
     * 画笔的宽度
     */
    private val mStrokeWidth = 2

    /**
     * 圆心坐标
     */
    private var mCenterX: Int = 0
    private var mCenterY: Int = 0
    private val mPaint: Paint

    /**
     * 箭头(小三角最长边的一半长度 = mArrawRate * mWidth / 2      */
    private val mArrowRate = 0.333f
    var arrowDegree = -1
    private val mArrowPath: Path
    /**
     * 内圆的半径 = mInnerCircleRadiusRate * mRadus
     */
    private val mInnerCircleRadiusRate = 0.3f

    /**
     * GestureLockView的三种状态
     */
    enum class Mode {
        STATUS_NO_FINGER, STATUS_FINGER_ON, STATUS_FINGER_UP
    }

    init {
        Log.i("GestureLockView:", "构造方法被调用")
        this.setWillNotDraw(false)
        mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mArrowPath = Path()

    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        Log.i("GestureLockView:", "onMeasure 方法被调用")
        mWidth = View.MeasureSpec.getSize(widthMeasureSpec)
        mHeight = View.MeasureSpec.getSize(heightMeasureSpec)

        // 取长和宽中的小值
        mWidth = if (mWidth < mHeight) mWidth else mHeight
        mCenterY = mWidth / 2
        mCenterX = mCenterY
        mRadius = mCenterX
        mRadius -= mStrokeWidth / 2

        // 绘制三角形,初始时是个默认箭头朝上的一个等腰三角形,用户绘制结束后,根据由两个GestureLockView决定需要旋转多少度
        val mArrowLength = mWidth / 2 * mArrowRate
        mArrowPath.moveTo((mWidth / 2).toFloat(), (mStrokeWidth + 2).toFloat())
        mArrowPath.lineTo(mWidth / 2 - mArrowLength, mStrokeWidth.toFloat() + 2f
                + mArrowLength)
        mArrowPath.lineTo(mWidth / 2 + mArrowLength, mStrokeWidth.toFloat() + 2f
                + mArrowLength)
        mArrowPath.close()
        mArrowPath.fillType = Path.FillType.WINDING

    }

    override fun onDraw(canvas: Canvas) {
        Log.i("GestureLockView:", "onDraw 方法被调用")
        when (mCurrentStatus) {
            GestureLockView.Mode.STATUS_FINGER_ON -> {

                // 绘制外圆
                mPaint.style = Style.STROKE
                mPaint.color = mColorFingerOn
                mPaint.strokeWidth = 2f
                canvas.drawCircle(mCenterX.toFloat(), mCenterY.toFloat(), mRadius.toFloat(), mPaint)
                // 绘制内圆
                mPaint.style = Style.FILL
                canvas.drawCircle(mCenterX.toFloat(), mCenterY.toFloat(), mRadius * mInnerCircleRadiusRate, mPaint)
            }
            GestureLockView.Mode.STATUS_FINGER_UP -> {
                // 绘制外圆
                mPaint.color = mColorFingerUp
                mPaint.style = Style.STROKE
                mPaint.strokeWidth = 2f
                canvas.drawCircle(mCenterX.toFloat(), mCenterY.toFloat(), mRadius.toFloat(), mPaint)
                // 绘制内圆
                mPaint.style = Style.FILL
                canvas.drawCircle(mCenterX.toFloat(), mCenterY.toFloat(), mRadius * mInnerCircleRadiusRate, mPaint)
            }

            GestureLockView.Mode.STATUS_NO_FINGER -> {

                // 绘制外圆
                mPaint.style = Style.FILL
                mPaint.color = mColorNoFingerOuter
                canvas.drawCircle(mCenterX.toFloat(), mCenterY.toFloat(), mRadius.toFloat(), mPaint)
                // 绘制内圆
                mPaint.color = mColorNoFingerInner
                canvas.drawCircle(mCenterX.toFloat(), mCenterY.toFloat(), mRadius * mInnerCircleRadiusRate, mPaint)
            }
        }

    }


    /**
     * 设置当前模式并重绘界面
     *
     * @param mode
     */
    fun setMode(mode: Mode) {
        this.mCurrentStatus = mode
        invalidate()
    }

    companion object {
        private val TAG = this::class.qualifiedName ?: "GestureLockView"
    }
}

xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:zhy="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.minlewan.testmimasuo.GestureLockViewGroup
        android:id="@+id/id_gestureLockViewGroup"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#F2F2F7"
        android:gravity="center_vertical"
        zhy:count="3"
        zhy:tryTimes="5" />
    <!--   zhy:color_no_finger_inner_circle="#ff085D58"
            zhy:color_no_finger_outer_circle="#ff08F0E0"
            zhy:color_finger_on="#FF1734BF" -->

</RelativeLayout>

MainActivity

package com.example.minlewan.testmimasuo

import android.os.Bundle
import com.example.minlewan.testmimasuo.GestureLockViewGroup.OnGestureLockViewListener
import android.app.Activity
import android.util.Log
import android.view.View


class MainActivity : Activity() {

    private var mGestureLockViewGroup: GestureLockViewGroup? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mGestureLockViewGroup = findViewById<View>(R.id.id_gestureLockViewGroup) as GestureLockViewGroup?
        mGestureLockViewGroup!!.setAnswer(intArrayOf(1, 2, 3, 4, 5)) //设置解锁密码
        mGestureLockViewGroup!!
                .setOnGestureLockViewListener(object : OnGestureLockViewListener {

                    override fun onUnmatchedExceedBoundary() {
                        Log.i("test","错误5...")
                        mGestureLockViewGroup!!.setUnMatchExceedBoundary(5)
                    }

                    override fun onGestureEvent(mChoose:List<Int>) {
                        Log.i("test","密码"+ mChoose.toString())
//                        mGestureLockViewGroup!!.checkAnswer()//检查解锁密码
                        mGestureLockViewGroup!!.reset()// 重置
                    }

                    override fun onBlockSelected(cId: Int) {
//                        Log.i("test","点击了"+cId)
                    }
                })
    }

}

注意监听方法onGestureEvent() 中参数就是 密码

无意要分,选择了最少的2分

csdn代码地址:http://download.csdn.net/download/minle_/10215143 

github求给个星星,不给也理解。我也不是大神,只为简历有一天能附上github

github地址:https://github.com/WanMinle/Gesture-lock-Kotlin-



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值