在移动设备上,手势解锁已经成为一种盛行的安全办法。在本文中,咱们将具体介绍怎么运用自定义视图实现手势解锁功用。

1. 创立 GestureLockView 类

首要,咱们需要创立一个名为 GestureLockView 的类,继承自 View。这个类将担任制作手势解锁视图,并处理用户的接触事情。

class GestureLockView @JvmOverloads constructor(
    context: Context, attrs:
    AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    // 行数
    private val numRows = 3
    // 列数
    private val numColumns = 3
    private val mStrokeWidth = 10.dpToPx
    //画笔
    private val mPaint = Paint().apply {
        isAntiAlias = true
        strokeWidth = mStrokeWidth
        style = Paint.Style.FILL
    }
    //手势途径
    private val mPath = Path()
    //可视化的九宫格点
    private val allPoints: MutableList<PointF> = mutableListOf()
    //可视化的九宫格点
    private val choosePoints: MutableList<PointF> = mutableListOf()
    //圆点的半径
    private var pointRadius = 0f
    //手指最终移动的方位
    private var lastX = 0f
    private var lastY = 0f
    // ...
}

在这段代码中,咱们定义了一些私有属性和变量。行数和列数分别为 3,表示手势解锁界面是一个 3×3 的九宫格。mStrokeWidth 表示九宫格线的宽度。mPaint 是用于制作九宫格的画笔。mPath 是手势途径,用于制作用户划过的途径。allPoints 是可视化的九宫格点的调集,choosePoints 是用户选择的点的调集。pointRadius 是圆点的半径。lastX 和 lastY 是手指最终一次接触的方位。

2. 初始化手势解锁视图

接下来,在 onSizeChanged 办法中初始化手势解锁视图。

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    val offset = w / (numRows + 1).toFloat()
    pointRadius = offset / 4f
    for (y in 0 until numRows) {
        for (x in 0 until numColumns) {
            allPoints.add(PointF(offset + offset * x, offset + offset * y))
        }
    }
}

在这段代码中,咱们首要核算出九宫格每个点之间的间隔 offset,然后依据 offset 核算出圆点的半径 pointRadius。接着运用两个嵌套的循环,将所有的九宫格点添加到 allPoints 调集中。

3. 制作手势解锁视图

在 onDraw 办法中,咱们运用 Canvas 和 Paint 目标来制作手势解锁视图。

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    allPoints.forEach {
        if (choosePoints.contains(it)) {
            mPaint.color = Color.BLUE
        } else {
            mPaint.color = Color.WHITE
        }
        mPaint.style = Paint.Style.FILL
        mPaint.alpha = 255
        //制作中心的实心圆
        canvas.drawCircle(it.x, it.y, pointRadius / 2f, mPaint)
        mPaint.alpha = 153
        canvas.drawCircle(it.x, it.y, pointRadius, mPaint)
    }
    mPaint.alpha = 255
    mPath.reset()
    choosePoints.takeIf { it.isNotEmpty() }?.run {
        forEach {
            mPath.takeIf { it.isEmpty }?.run {
                moveTo(it.x, it.y)
            } ?: run {
                mPath.lineTo(it.x, it.y)
            }
        }
        mPath.lineTo(lastX, lastY)
        //制作途径
        mPaint.color = Color.BLUE
        mPaint.style = Paint.Style.STROKE
        canvas.drawPath(mPath, mPaint)
    }
}

这段代码中,咱们先依据用户选择的点决定圆点的颜色,并制作实心和空心圆。然后,咱们运用 mPath 跟踪用户选择的途径,并依据途径制作手势解锁的途径。最终,咱们运用 canvas 的 drawPath 办法将途径制作到屏幕上。

4. 处理接触事情

在 onTouchEvent 办法中,咱们处理用户的接触事情,并依据用户的操作更新视图。

@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            lastX = event.x
            lastY = event.y
            allPoints.forEach {
                //接触的点和九宫格密码圆相交
                if (abs(it.x - lastX) < pointRadius && abs(it.y - lastY) < pointRadius) {
                    choosePoints.add(it)
                }
            }
        }
        MotionEvent.ACTION_MOVE -> {
            lastX = event.x
            lastY = event.y
            allPoints.forEach {
                //接触的点和九宫格密码圆相交
                if (distanceFromPath(mPath, it.x, it.y) < pointRadius + mStrokeWidth / 2f && choosePoints.contains(it).not()
                    || (mPath.isEmpty && abs(it.x - lastX) < pointRadius && abs(it.y - lastY) < pointRadius)
                ) {
                    choosePoints.add(it)
                    lastX = it.x
                    lastY = it.y
                }
            }
        }
        MotionEvent.ACTION_UP -> {
            choosePoints.clear()
            mPath.reset()
        }
    }
    invalidate()
    return true
}

在这段代码中,咱们依据用户的接触事情类型,更新 choosePoints 和 mPath 目标。在 MotionEvent.ACTION_DOWN 中,咱们依据用户接触的方位判断是否与九宫格的圆点相交,并将相交的圆点添加到 choosePoints 调集中。在 MotionEvent.ACTION_MOVE 中,咱们依据用户的接触方位判断是否与九宫格的圆点相交,并更新 choosePoints 调集和最终接触的方位 lastX 和 lastY。在 MotionEvent.ACTION_UP 中,咱们清空 choosePoints 和 mPath 目标,表示用户完成了一次手势解锁操作。最终,咱们调用 invalidate 办法,告诉系统从头制作视图。

5. 辅助办法

在代码的末尾,咱们还定义了一个辅助办法 distanceFromPath,用于核算一个点到途径的最短间隔。

private fun distanceFromPath(path: Path, x: Float, y: Float): Float {
    val pathMeasure = PathMeasure(path, false)
    val pos = FloatArray(2)
    val tan = FloatArray(2)
    var distance = Float.MAX_VALUE
    var i = 0f
    while (i <= pathMeasure.length) {
        if (pathMeasure.getPosTan(i, pos, tan)) {
            val dx = pos[0] - x
            val dy = pos[1] - y
            val d = sqrt((dx * dx + dy * dy).toDouble()).toFloat()
            if (d < distance) {
                distance = d
            }
        }
        i += 1f
    }
    return distance
}

这个办法运用 PathMeasure 来测量途径的长度,并逐步遍历途径上的点,核算每个点到给定点的间隔。最终返回最短的间隔。

总结

在本文中,咱们具体解说了怎么创立一个自定义的手势解锁视图。咱们经过制作九宫格点和用户选择的途径,实现了手势解锁的功用。希望这篇文章能够帮助你了解并实现自定义视图的过程。如果你对该主题有更多的兴趣,能够进一步探究 Android 的绘图 API 和接触事情处理机制。