溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android如何實(shí)現(xiàn)九宮格手勢(shì)密碼

發(fā)布時(shí)間:2022-06-28 13:59:08 來源:億速云 閱讀:284 作者:iii 欄目:開發(fā)技術(shù)

這篇“Android如何實(shí)現(xiàn)九宮格手勢(shì)密碼”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Android如何實(shí)現(xiàn)九宮格手勢(shì)密碼”文章吧。

先見圖

Android如何實(shí)現(xiàn)九宮格手勢(shì)密碼

思路:首先是9個(gè)格子,接著是格子連線;那么我們的步驟就有了。

1.手勢(shì)監(jiān)聽,進(jìn)行連線
2.格子的狀態(tài)未連接(初始狀態(tài))、已連接的(沒有結(jié)果前)、錯(cuò)誤狀態(tài)(有結(jié)果后)。(先這三個(gè),可擴(kuò)展,比如按下狀態(tài))
3.自定義viewgroup作為九宮格的容器,里面包含9個(gè)view(小格子)

一、先從簡(jiǎn)單的說起吧,9個(gè)小格子以及狀態(tài)

為了擴(kuò)展性,不自定義view,將三個(gè)狀態(tài)和有關(guān)屬性提取

1.提取屬性,代碼如下: 

class NineChildInf {
        /**
         * 當(dāng)前所在9宮格的位置
         * 從1開始
         */
        var index = 0
        /**
         * 是否被點(diǎn)亮
         */
        var isLight = false
        /**
         * 中心點(diǎn)所在父類容器內(nèi)的坐標(biāo)
         */
        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.三個(gè)狀態(tài),代碼如下

/**
 * Created by XinHeng on 2019/02/27.
 * describe:9宮格子view必須實(shí)現(xiàn)此接口
 */
abstract class NineChildParent<T : View>(var view: T) {
    protected open var context = view.context.applicationContext
    val NINE_CHILD_INF = NineChildInf()
    /**
     * 密碼錯(cuò)誤時(shí)的顯示
     */
    abstract fun setErrorStatue()
 
    /**
     * 被選中時(shí)的顯示
     */
    abstract fun setLightStatue()
 
    /**
     * 默認(rèn)顯示
     */
    abstract fun setDefaultStatue()
 
}

二、自定義九宮格容器,NineViewGroup。

既然是九宮格,那自然少不了這些屬性,水平間隔、垂直間隔、最小有效連接數(shù)、當(dāng)前狀態(tài)、密碼是否設(shè)置完成等。還需要將開啟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
    /**
     * 連線最小有效數(shù)字
     */
    var minEffectiveSize = 4
    /**
     * 小格子的寬高
     */
    private var childSlide: Int = 30
    private val ERROR_STATUE = 2
    private val LINKING_STATUE = 1
    private val DEFAULT_STATUE = 0
    /**
     * 當(dāng)前狀態(tài)
     * 0->最初狀態(tài) DEFAULT_STATUE
     * 1->正在連線中 LINKING_STATUE
     * 2->錯(cuò)誤狀態(tài) ERROR_STATUE
     */
    private var nowStatue = DEFAULT_STATUE
    /**
     * 一次密碼設(shè)置完成標(biāo)志
     */
    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 {
        //使能調(diào)用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)
        //又忘了計(jì)算子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
                }
            }
        }
    }
}

三、手勢(shì)監(jiān)聽、連線

1.手勢(shì)監(jiān)聽,重寫onTouchEvent()方法,必要需要時(shí)重寫onInterceptTouchEvent()方法進(jìn)行攔截(跟情況而定,這里就不多說了)。簡(jiǎn)單的三個(gè)手勢(shì)狀態(tài)按下、移動(dòng)、抬起。在各個(gè)狀態(tài)下,記錄坐標(biāo),并且更新子view(小格子)的ui,還有線條。代碼片段如下:

override fun onTouchEvent(event: MotionEvent): Boolean {
        if (childCount == 0 || complete) {
            return super.onTouchEvent(event)
        }
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                //記錄落點(diǎn)
                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
                //統(tǒng)計(jì)
                upUpdateChild()
            }
        }
        return true
    }

2.連線,在容器的onDraw()方法,進(jìn)行畫線操作,代碼片段如下:

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)
        }
    }

四、進(jìn)行到這一步,大致的步驟就是這了。

但是還有一些細(xì)節(jié):比如連線中需要判斷中間是否含有小格子、判斷觸點(diǎn)是否在小格子上、連接完成后的回調(diào)、錯(cuò)誤狀態(tài)顯示、恢復(fù)初始狀態(tài)等。粘出部分代碼片段(這些只是能實(shí)現(xiàn)效果,還可以優(yōu)化,交給大家了):

1.判斷觸點(diǎn)是否在小格子上

private fun childContains(x: Float, y: Float): Boolean {
        (0 until childCount).forEach {
            var childAt = getChildAt(it)
            //這一句,循環(huán)判斷,是否屬于其范圍
            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狀態(tài)
                    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
    /**
     * 是否有第一個(gè)選中
     */
    private var firstSelect = true
    private val ERROR_STATUE = 2
    private val LINKING_STATUE = 1
    private val DEFAULT_STATUE = 0
    /**
     * 是否顯示線條
     */
    var showLine = false
    /**
     * 連線最小有效數(shù)字
     */
    var minEffectiveSize = 4
    /**
     * 當(dāng)前狀態(tài)
     * 0->最初狀態(tài) DEFAULT_STATUE
     * 1->正在連線中 LINKING_STATUE
     * 2->錯(cuò)誤狀態(tài) ERROR_STATUE
     */
    private var nowStatue = DEFAULT_STATUE
    /**
     * 一次密碼設(shè)置完成標(biāo)志
     */
    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 {
        //使能調(diào)用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)
        //又忘了計(jì)算子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 -> {
                //記錄落點(diǎn)
                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
                //統(tǒng)計(jì)
                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())
    }
 
    /**
     * 錯(cuò)誤狀態(tài)展示
     */
    fun showErrorStatue() {
        nowStatue = ERROR_STATUE
        points.forEach {
            it.setErrorStatue()
        }
        invalidate()
        resetStatueDelayed(500)
    }
 
    /**
     * 恢復(fù)初始狀態(tài)
     */
    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狀態(tài)
                    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<*>
 
        /**
         * 密碼設(shè)置結(jié)束
         * @param effective 是否有效
         * @param password 密碼
         */
        fun complete(effective: Boolean, password: String)
    }
}

以上就是關(guān)于“Android如何實(shí)現(xiàn)九宮格手勢(shì)密碼”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI