Android实现九宫格手势密码

Shanon ·
更新时间:2024-09-20
· 16 次阅读

本文实例为大家分享了Android实现九宫格手势密码的具体代码,供大家参考,具体内容如下

介绍下自己编写的九宫格手势密码。先见图

思路:首先是9个格子,接着是格子连线;那么我们的步骤就有了。

1.手势监听,进行连线
2.格子的状态未连接(初始状态)、已连接的(没有结果前)、错误状态(有结果后)。(先这三个,可扩展,比如按下状态)
3.自定义viewgroup作为九宫格的容器,里面包含9个view(小格子)

一、先从简单的说起吧,9个小格子以及状态

为了扩展性,不自定义view,将三个状态和有关属性提取

1.提取属性,代码如下: 

class NineChildInf {         /**          * 当前所在9宫格的位置          * 从1开始          */         var index = 0         /**          * 是否被点亮          */         var isLight = false         /**          * 中心点所在父类容器内的坐标          */         var centerX = 0.toFloat()         var centerY = 0.toFloat()         fun setContent(index: Int, centerX: Float, centerY: Float) {             this.index = index             this.centerX = centerX             this.centerY = centerY         }         constructor()         fun updateCenterPoint(x: Float, y: Float) {             this.centerX = x             this.centerY = y         }         fun reset() {             this.index = 0             this.centerX = 0f             this.centerY = 0f             this.isLight = false         }         override fun toString(): String {             return "NineChildInf(index=$index, isLight=$isLight, centerX=$centerX, centerY=$centerY)"         }     }

2.三个状态,代码如下

/**  * Created by XinHeng on 2019/02/27.  * describe:9宫格子view必须实现此接口  */ abstract class NineChildParent<T : View>(var view: T) {     protected open var context = view.context.applicationContext     val NINE_CHILD_INF = NineChildInf()     /**      * 密码错误时的显示      */     abstract fun setErrorStatue()     /**      * 被选中时的显示      */     abstract fun setLightStatue()     /**      * 默认显示      */     abstract fun setDefaultStatue() } 二、自定义九宫格容器,NineViewGroup。

既然是九宫格,那自然少不了这些属性,水平间隔、垂直间隔、最小有效连接数、当前状态、密码是否设置完成等。还需要将开启viewgroup的onDraw()方法。具体代码如下:

class NineViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ViewGroup(context, attrs, defStyleAttr) {     /**      * 水平间的间隔      */     private var paddingH = 60     /**      * 垂直间的间隔      */     private var paddingV = 60     /**      * 连线最小有效数字      */     var minEffectiveSize = 4     /**      * 小格子的宽高      */     private var childSlide: Int = 30     private val ERROR_STATUE = 2     private val LINKING_STATUE = 1     private val DEFAULT_STATUE = 0     /**      * 当前状态      * 0->最初状态 DEFAULT_STATUE      * 1->正在连线中 LINKING_STATUE      * 2->错误状态 ERROR_STATUE      */     private var nowStatue = DEFAULT_STATUE     /**      * 一次密码设置完成标志      */     private var complete = false     /**      * 线条宽度      */     private var lineWidth = 5     private var lineColor = Color.parseColor("#33b5e5")     private var errorLineColor = Color.RED     private var childViews = ArrayList<NineChildParent<*>>(9)     init {         //使能调用onDraw()方法         setWillNotDraw(false)         var array = context.obtainStyledAttributes(attrs, R.styleable.NineViewGroup, defStyleAttr, 0)         (0..array.indexCount).forEach {             var index = array.getIndex(it)             when (index) {                 R.styleable.NineViewGroup_nine_child_size -> childSlide = array.getDimensionPixelSize(index, childSlide)                 R.styleable.NineViewGroup_nine_line_color -> lineColor = array.getColor(index, lineColor)                 R.styleable.NineViewGroup_nine_error_line_color -> errorLineColor = array.getColor(index, errorLineColor)                 R.styleable.NineViewGroup_nine_effective_size -> minEffectiveSize = array.getInt(index, minEffectiveSize)                 R.styleable.NineViewGroup_nine_padding_h -> paddingH = array.getDimensionPixelSize(index, paddingH)                 R.styleable.NineViewGroup_nine_padding_v -> paddingV = array.getDimensionPixelSize(index, paddingV)                 R.styleable.NineViewGroup_nine_line_width -> lineWidth = array.getDimensionPixelSize(index, lineWidth)             }         }         array.recycle()     }     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {         var width = childSlide * 3 + paddingLeft + paddingRight + paddingH * 2         var height = childSlide * 3 + paddingTop + paddingBottom + paddingV * 2         setMeasuredDimension(width, height)         //又忘了计算子view的大小了。。。         measureChildren(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))     }     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {         var childView: View         var top: Int = paddingTop         var left: Int = paddingLeft         var right: Int         var bottom: Int         if (childCount > 0) {             (0 until childCount).forEach {                 childView = getChildAt(it)                 right = left + childView.measuredWidth                 bottom = top + childView.measuredHeight                 //Log.e("TAG", "onLayout: $left $top $right $bottom")                 var nineChildInf = (childViews[it]).NINE_CHILD_INF                 nineChildInf.setContent(it + 1, (left + right) / 2f, (top + bottom) / 2f)                 //Log.e("TAG", "onLayout: child=$nineChildInf")                 childView.layout(left, top, right, bottom)                 if ((it + 1) % 3 == 0) {                     left = paddingLeft                     top = bottom + paddingV                 } else {                     left = right + paddingH                 }             }         }     } } 三、手势监听、连线

1.手势监听,重写onTouchEvent()方法,必要需要时重写onInterceptTouchEvent()方法进行拦截(跟情况而定,这里就不多说了)。简单的三个手势状态按下、移动、抬起。在各个状态下,记录坐标,并且更新子view(小格子)的ui,还有线条。代码片段如下:

override fun onTouchEvent(event: MotionEvent): Boolean {         if (childCount == 0 || complete) {             return super.onTouchEvent(event)         }         when (event.action) {             MotionEvent.ACTION_DOWN -> {                 //记录落点                 lastX = event.x                 lastY = event.y                 downUpdateChild(lastX, lastY)             }             MotionEvent.ACTION_MOVE -> {                 lastX = event.x                 lastY = event.y                 moveUpdateChild(lastX, lastY)             }             MotionEvent.ACTION_UP -> {                 complete = true                 //统计                 upUpdateChild()             }         }         return true     }

2.连线,在容器的onDraw()方法,进行画线操作,代码片段如下:

override fun onDraw(canvas: Canvas) {         super.onDraw(canvas)         if (!showLine) {             return         }         paint.color = when (nowStatue) {             ERROR_STATUE -> errorLineColor             else -> lineColor         }         if (points.size > 1) {             (1 until points.size).forEach {                 var pointXYStart = points[it - 1].NINE_CHILD_INF                 var pointXYEnd = points[it].NINE_CHILD_INF                 canvas.drawLine(pointXYStart.centerX, pointXYStart.centerY, pointXYEnd.centerX, pointXYEnd.centerY, paint)             }         }         if (lastX > 0 && points.size > 0) {             var pointXY = points[points.size - 1].NINE_CHILD_INF             canvas.drawLine(pointXY.centerX, pointXY.centerY, lastX, lastY, paint)         }     } 四、进行到这一步,大致的步骤就是这了。

但是还有一些细节:比如连线中需要判断中间是否含有小格子、判断触点是否在小格子上、连接完成后的回调、错误状态显示、恢复初始状态等。粘出部分代码片段(这些只是能实现效果,还可以优化,交给大家了):

1.判断触点是否在小格子上

private fun childContains(x: Float, y: Float): Boolean {         (0 until childCount).forEach {             var childAt = getChildAt(it)             //这一句,循环判断,是否属于其范围             if (x >= childAt.left && x < childAt.right && y >= childAt.top && y < childAt.bottom) {                 return if (!childViews[it].NINE_CHILD_INF.isLight) {                     if (points.size > 0) {                         checkMiddleChild(points[points.size - 1], childViews[it])?.run {                             if (!NINE_CHILD_INF.isLight) {                                 buffer.append(NINE_CHILD_INF.index)                                 changeLightStatue(this)                             }                         }                     }                     buffer.append(it + 1)                     //TODO 改变子view的UI状态                     changeLightStatue(childViews[it])                     true                 } else {                     false                 }             }         }         return false     }

2.判断中间是否含有小格子

private fun checkMiddleChild(nineChildParent: NineChildParent<*>, nineChildParent1: NineChildParent<*>): NineChildParent<*>? {         var index = nineChildParent.NINE_CHILD_INF.index         var index1 = nineChildParent1.NINE_CHILD_INF.index         var sum = index + index1         if (sum == 10) {             return childViews[4]         } else if (index % 2 != 0 && index1 % 2 != 0) {             if ((sum == 4 || sum == 16) || (sum == 8 && (index == 1 || index1 == 1))||(sum == 12 && (index == 3 || index1 == 3)))                 return childViews[sum / 2 - 1]         }         return null     } 五、如有bug欢迎留言指出,下面粘出九宫格容器的全部代码。 /**  * Created by XinHeng on 2019/01/29.  * describe:九宫格的容器  */ class NineViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ViewGroup(context, attrs, defStyleAttr) {     /**      * 水平间的间隔      */     private var paddingH = 60     /**      * 垂直间的间隔      */     private var paddingV = 60     /**      * 是否有第一个选中      */     private var firstSelect = true     private val ERROR_STATUE = 2     private val LINKING_STATUE = 1     private val DEFAULT_STATUE = 0     /**      * 是否显示线条      */     var showLine = false     /**      * 连线最小有效数字      */     var minEffectiveSize = 4     /**      * 当前状态      * 0->最初状态 DEFAULT_STATUE      * 1->正在连线中 LINKING_STATUE      * 2->错误状态 ERROR_STATUE      */     private var nowStatue = DEFAULT_STATUE     /**      * 一次密码设置完成标志      */     private var complete = false     /**      * 线条宽度      */     private var lineWidth = 5     private var lastX: Float = 0f     private var lastY: Float = 0f     private var buffer = StringBuilder()     private var points = ArrayList<NineChildParent<*>>(9)     private var childViews = ArrayList<NineChildParent<*>>(9)     /**      * 小格子的宽高      */     private var childSlide: Int = 30     private var lineColor = Color.parseColor("#33b5e5")     private var errorLineColor = Color.RED     var onNineViewGroupListener: OnNineViewGroupListener? = null         set(value) {             field = value             value?.let {                 setChildMode(it)             }         }     private val paint = Paint().apply {         isAntiAlias = true         isDither = true     }     init {         //使能调用onDraw()方法         setWillNotDraw(false)         var array = context.obtainStyledAttributes(attrs, R.styleable.NineViewGroup, defStyleAttr, 0)         (0..array.indexCount).forEach {             var index = array.getIndex(it)             when (index) {                 R.styleable.NineViewGroup_nine_child_size -> childSlide = array.getDimensionPixelSize(index, childSlide)                 R.styleable.NineViewGroup_nine_line_color -> lineColor = array.getColor(index, lineColor)                 R.styleable.NineViewGroup_nine_error_line_color -> errorLineColor = array.getColor(index, errorLineColor)                 R.styleable.NineViewGroup_nine_effective_size -> minEffectiveSize = array.getInt(index, minEffectiveSize)                 R.styleable.NineViewGroup_nine_padding_h -> paddingH = array.getDimensionPixelSize(index, paddingH)                 R.styleable.NineViewGroup_nine_padding_v -> paddingV = array.getDimensionPixelSize(index, paddingV)                 R.styleable.NineViewGroup_nine_show_line -> showLine = array.getBoolean(index, showLine)                 R.styleable.NineViewGroup_nine_line_width -> lineWidth = array.getDimensionPixelSize(index, lineWidth)             }         }         array.recycle()         paint.strokeWidth = lineWidth.toFloat()     }     private fun setChildMode(onNineViewGroupListener: OnNineViewGroupListener) {         removeAllViews()         childViews.clear()         (0..8).forEach {             var mode = onNineViewGroupListener.getChildMode()             mode.NINE_CHILD_INF.index = it + 1             mode.setDefaultStatue()             addView(mode.view, getLp())             childViews.add(mode)         }     }     private fun getLp(): LayoutParams {         return LayoutParams(childSlide, childSlide)     }     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {         var width = childSlide * 3 + paddingLeft + paddingRight + paddingH * 2         var height = childSlide * 3 + paddingTop + paddingBottom + paddingV * 2         setMeasuredDimension(width, height)         //又忘了计算子view的大小了。。。         measureChildren(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))     }     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {         var childView: View         var top: Int = paddingTop         var left: Int = paddingLeft         var right: Int         var bottom: Int         if (childCount > 0) {             (0 until childCount).forEach {                 childView = getChildAt(it)                 right = left + childView.measuredWidth                 bottom = top + childView.measuredHeight                 //Log.e("TAG", "onLayout: $left $top $right $bottom")                 var nineChildInf = (childViews[it]).NINE_CHILD_INF                 nineChildInf.setContent(it + 1, (left + right) / 2f, (top + bottom) / 2f)                 //Log.e("TAG", "onLayout: child=$nineChildInf")                 childView.layout(left, top, right, bottom)                 if ((it + 1) % 3 == 0) {                     left = paddingLeft                     top = bottom + paddingV                 } else {                     left = right + paddingH                 }             }         }     }     override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {         return true     }     override fun onTouchEvent(event: MotionEvent): Boolean {         if (childCount == 0 || complete) {             return super.onTouchEvent(event)         }         when (event.action) {             MotionEvent.ACTION_DOWN -> {                 //记录落点                 lastX = event.x                 lastY = event.y                 downUpdateChild(lastX, lastY)             }             MotionEvent.ACTION_MOVE -> {                 lastX = event.x                 lastY = event.y                 moveUpdateChild(lastX, lastY)             }             MotionEvent.ACTION_UP -> {                 complete = true                 //统计                 upUpdateChild()             }         }         return true     }     private fun downUpdateChild(x: Float, y: Float) {         firstSelect = childContains(x, y)     }     private fun moveUpdateChild(x: Float, y: Float) {         if (firstSelect) {             moveUpdateLineAndChildView(x, y)         } else {             downUpdateChild(x, y)         }     }     private fun moveUpdateLineAndChildView(x: Float, y: Float) {         if (points.size != childCount)             childContains(x, y)         invalidate()     }     private fun upUpdateChild() {         var effective = points.size >= minEffectiveSize         onNineViewGroupListener?.complete(effective, buffer.toString())     }     /**      * 错误状态展示      */     fun showErrorStatue() {         nowStatue = ERROR_STATUE         points.forEach {             it.setErrorStatue()         }         invalidate()         resetStatueDelayed(500)     }     /**      * 恢复初始状态      */     private fun resetStatue() {         points.clear()         firstSelect = false         lastX = 0f         lastY = 0f         buffer.clear()         nowStatue = DEFAULT_STATUE         (0 until childCount).forEach {             var nineChildParent = childViews[it]             nineChildParent.setDefaultStatue()             nineChildParent.NINE_CHILD_INF.isLight = false         }         invalidate()         complete = false     }     fun resetStatueDelayed(time: Int) {         postDelayed({ resetStatue() }, time.toLong())     }     private fun childContains(x: Float, y: Float): Boolean {         (0 until childCount).forEach {             var childAt = getChildAt(it)             if (x >= childAt.left && x < childAt.right && y >= childAt.top && y < childAt.bottom) {                 return if (!childViews[it].NINE_CHILD_INF.isLight) {                     if (points.size > 0) {                         checkMiddleChild(points[points.size - 1], childViews[it])?.run {                             if (!NINE_CHILD_INF.isLight) {                                 buffer.append(NINE_CHILD_INF.index)                                 changeLightStatue(this)                             }                         }                     }                     buffer.append(it + 1)                     //TODO 改变子view的UI状态                     changeLightStatue(childViews[it])                     true                 } else {                     false                 }             }         }         return false     }     private fun changeLightStatue(childParent: NineChildParent<*>) {         childParent.NINE_CHILD_INF.isLight = true         childParent.setLightStatue()         points.add(childParent)//记录     }     private fun checkMiddleChild(nineChildParent: NineChildParent<*>, nineChildParent1: NineChildParent<*>): NineChildParent<*>? {         var index = nineChildParent.NINE_CHILD_INF.index         var index1 = nineChildParent1.NINE_CHILD_INF.index         var sum = index + index1         if (sum == 10) {             return childViews[4]         } else if (index % 2 != 0 && index1 % 2 != 0) {             if ((sum == 4 || sum == 16) || (sum == 8 && (index == 1 || index1 == 1))||(sum == 12 && (index == 3 || index1 == 3)))                 return childViews[sum / 2 - 1]         }         return null     }     override fun onDraw(canvas: Canvas) {         super.onDraw(canvas)         if (!showLine) {             return         }         paint.color = when (nowStatue) {             ERROR_STATUE -> errorLineColor             else -> lineColor         }         if (points.size > 1) {             (1 until points.size).forEach {                 var pointXYStart = points[it - 1].NINE_CHILD_INF                 var pointXYEnd = points[it].NINE_CHILD_INF                 canvas.drawLine(pointXYStart.centerX, pointXYStart.centerY, pointXYEnd.centerX, pointXYEnd.centerY, paint)             }         }         if (lastX > 0 && points.size > 0) {             var pointXY = points[points.size - 1].NINE_CHILD_INF             canvas.drawLine(pointXY.centerX, pointXY.centerY, lastX, lastY, paint)         }     }     interface OnNineViewGroupListener {         /**          * 子view          */         fun getChildMode(): NineChildParent<*>         /**          * 密码设置结束          * @param effective 是否有效          * @param password 密码          */         fun complete(effective: Boolean, password: String)     } }



手势 Android

需要 登录 后方可回复, 如果你还没有账号请 注册新账号