Android实现九宫格手势密码

2022-10-07,,,

本文实例为大家分享了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实现九宫格手势密码.doc》

下载本文的Word格式文档,以方便收藏与打印。