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) /** * 保存用户选中的GestureLockView的id */ 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-