本文实例为大家分享了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) } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。