安卓9宫格密码键盘

自定义组件
 

package com.zx.mocab.views;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * 九宫格解锁控件
 */
public class LockPatternView extends View {
    // 定义画笔
    private Paint normalPaint; // 正常状态的画笔
    private Paint pressPaint; // 按下状态的画笔
    private Paint errorPaint; // 错误状态的画笔
    private Paint arrowPaint; // 箭头的画笔
    private Paint linePaint; // 连线的画笔

    // 定义颜色
    private final int outerPressColor = 0xff8cbad8; // 按下状态外圈颜色
    private final int innerPressColor = 0xff0596f6; // 按下状态内圈颜色
    private final int outerNormalColor = 0xffd9d9d9; // 正常状态外圈颜色
    private final int innerNormalColor = 0xff929292; // 正常状态内圈颜色
    private final int outerErrorColor = 0xff901032; // 错误状态外圈颜色
    private final int innerErrorColor = 0xff901032; // 错误状态内圈颜色

    private String setPassword = null; // 预设的解锁密码
    private Point[][] points = new Point[3][3]; // 九宫格中的点
    private float outerRadius = 0f; // 外圈半径
    private boolean isTouchPoint = false; // 是否触摸到点上
    private List selectPoints = new ArrayList<>(); // 已选择的点
    private boolean isInit = false; // 是否初始化

    private float moveX = 0f; // 移动时的X坐标
    private float moveY = 0f; // 移动时的Y坐标

    private LockPatternCallBack lockPatternCallBack = null; // 回调接口

    public LockPatternView(Context context) {
        this(context, null);
    }

    public LockPatternView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LockPatternView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        normalPaint = new Paint();
        normalPaint.setAntiAlias(true);
        normalPaint.setStyle(Paint.Style.STROKE);
        normalPaint.setStrokeWidth(outerRadius / 9);

        pressPaint = new Paint();
        pressPaint.setAntiAlias(true);
        pressPaint.setStyle(Paint.Style.STROKE);
        pressPaint.setStrokeWidth(outerRadius / 6);

        errorPaint = new Paint();
        errorPaint.setAntiAlias(true);
        errorPaint.setStyle(Paint.Style.STROKE);
        errorPaint.setStrokeWidth(outerRadius / 6);

        arrowPaint = new Paint();
        arrowPaint.setAntiAlias(true);
        arrowPaint.setStyle(Paint.Style.FILL);
        arrowPaint.setColor(innerPressColor);

        linePaint = new Paint();
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setColor(innerPressColor);
        linePaint.setStrokeWidth(outerRadius / 9);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!isInit) {
            isInit = true;
            initPoints();
            initPaint();
        }
    }

    /**
     * 初始化九宫格中的点
     */
    private void initPoints() {
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        float offsetX = 0f;
        float offsetY = 0f;
        if (height > width) {
            offsetY = (height - width) / 2f;
            height = width;
        } else {
            offsetX = (width - height) / 2f;
            width = height;
        }
        int squareWidth = width / 3;

        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                points[i][j] = new Point(
                        offsetX + squareWidth * (j * 2 + 1) / 2,
                        offsetY + squareWidth * (i * 2 + 1) / 2,
                        i * 3 + j);
            }
        }

        outerRadius = width / 12f;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                Point point = points[i][j];
                if (point != null) {
                    if (point.isNormalStatus()) {
                        // 绘制普通状态的外圆和内圆
                        normalPaint.setColor(outerNormalColor);
                        canvas.drawCircle(point.centerX, point.centerY, outerRadius, normalPaint);
                        normalPaint.setColor(innerNormalColor);
                        canvas.drawCircle(point.centerX, point.centerY, outerRadius / 6, normalPaint);
                    }

                    if (point.isPressStatus()) {
                        // 绘制按下状态的外圆和内圆
                        pressPaint.setColor(outerPressColor);
                        canvas.drawCircle(point.centerX, point.centerY, outerRadius, pressPaint);
                        pressPaint.setColor(innerPressColor);
                        canvas.drawCircle(point.centerX, point.centerY, outerRadius / 6, pressPaint);
                    }

                    if (point.isErrorStatus()) {
                        // 绘制错误状态的外圆和内圆
                        errorPaint.setColor(outerErrorColor);
                        canvas.drawCircle(point.centerX, point.centerY, outerRadius, errorPaint);
                        errorPaint.setColor(innerErrorColor);
                        canvas.drawCircle(point.centerX, point.centerY, outerRadius / 6, errorPaint);
                    }
                }
            }
        }
        drawLineAndArrow(canvas); // 绘制连线和箭头
    }

    /**
     * 绘制连线和箭头
     */
    private void drawLineAndArrow(Canvas canvas) {
        if (selectPoints.size() < 1) {
            return;
        }
        Point lastPoint = selectPoints.get(0);
        for (int i = 1; i < selectPoints.size(); i++) {
            Point currentPoint = selectPoints.get(i);
            drawLine(lastPoint, currentPoint, canvas, linePaint); // 绘制连线
            drawArrow(canvas, arrowPaint, lastPoint, currentPoint, outerRadius / 5.0, 38); // 绘制箭头
            lastPoint = currentPoint;
        }

        // 如果手指移动但未触摸到任何点,继续绘制线条
        boolean isInnerPoint = MathUtil.checkInRound(lastPoint.centerX, lastPoint.centerY, outerRadius / 4, moveX, moveY);
        if (!isInnerPoint && isTouchPoint) {
            drawLine(lastPoint, new Point(moveX, moveY, -1), canvas, linePaint);
        }
    }

    /**
     * 绘制箭头
     */

    private void drawArrow(Canvas canvas, Paint arrowPaint, Point start, Point end, double arrowHeight, int angle) {
        double d = MathUtil.distance(start.centerX, start.centerY, end.centerX, end.centerY);
        float sinB = (float) ((end.centerX - start.centerX) / d);
        float cosB = (float) ((end.centerY - start.centerY) / d);
        float tanA = (float) Math.tan(Math.toRadians(angle));

        // h现在表示从起点到终点连线的长度减去外圈半径,以避免箭头与圆圈重叠
        float h = (float) (d - outerRadius * 1.1);
        float l = (float) (arrowHeight * tanA); // 箭头侧边的长度
        float a = l * sinB; // 计算侧边偏移量
        float b = l * cosB; // 计算侧边偏移量
        float x0 = h * sinB;
        float y0 = h * cosB;
        float x2 = start.centerX + x0 - b;
        float y2 = start.centerY + y0 + a;
        float x3 = start.centerX + x0 + b;
        float y3 = start.centerY + y0 - a;

        // 尖端坐标直接使用结束点坐标
        float x1 = end.centerX;
        float y1 = end.centerY;

        Path path = new Path();
        path.moveTo(x1, y1); // 从箭头尖端开始
        path.lineTo(x2, y2); // 连接到箭头的一侧
        path.lineTo(x3, y3); // 连接到箭头的另一侧
        path.close(); // 闭合路径形成箭头
        canvas.drawPath(path, arrowPaint); // 绘制箭头
    }
    /**
     * 绘制连线
     */
    private void drawLine(Point startPoint, Point endPoint, Canvas canvas, Paint linePaint) {
        double pointDistance = MathUtil.distance(startPoint.centerX, startPoint.centerY, endPoint.centerX, endPoint.centerY);
        double dx = endPoint.centerX - startPoint.centerX;
        double dy = endPoint.centerY - startPoint.centerY;
        float rx = (float) (dx / pointDistance * outerRadius / 6.0);
        float ry = (float) (dy / pointDistance * outerRadius / 6.0);
        canvas.drawLine(
                startPoint.centerX + rx,
                startPoint.centerY + ry,
                endPoint.centerX - rx,
                endPoint.centerY - ry,
                linePaint
        );
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        moveX = event.getX();
        moveY = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Point point = getTouchPoint();
                if (point != null) {
                    isTouchPoint = true;
                    selectPoints.add(point);
                    point.setStatusPress();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTouchPoint) {
                    point = getTouchPoint();
                    if (point != null && !selectPoints.contains(point)) {
                        selectPoints.add(point);
                        point.setStatusPress();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                isTouchPoint = false;
                checkPasswordRight(); // 检查密码是否正确
                break;
        }
        invalidate();
        return true;
    }

    /**
     * 检查密码是否正确,并进行相应处理
     */
    private void checkPasswordRight() {
        Log.e("TAG", getSelectPassword() + " --> " + setPassword);
        if (getSelectPassword().equals(setPassword)) {
            if (lockPatternCallBack != null) {
                lockPatternCallBack.checkSuccess();
            }
        } else {
            if (lockPatternCallBack != null) {
                lockPatternCallBack.checkError();
            }
            for (Point p : selectPoints) {
                p.setStatusError();
            }
            invalidate();
            delayResetStatus(); // 延迟重置状态
        }
    }

    /**
     * 延迟重置状态
     */
    private void delayResetStatus() {
        postDelayed(() -> {
            for (Point p : selectPoints) {
                p.setStatusNormal();
            }
            selectPoints.clear();
            invalidate();
        }, 1000);
    }

    /**
     * 获取触摸到的点
     */
    private Point getTouchPoint() {
        for (Point[] row : points) {
            for (Point point : row) {
                if (MathUtil.checkInRound(point.centerX, point.centerY, outerRadius, moveX, moveY)) {
                    return point;
                }
            }
        }
        return null;
    }

    /**
     * 设置默认密码
     */
    public void setDefaultPassWord(String password) {
        this.setPassword = password;
    }

    /**
     * 获取用户绘制的密码
     */
    private String getSelectPassword() {
        StringBuilder pass = new StringBuilder();
        for (Point p : selectPoints) {
            pass.append(p.index);
        }
        return pass.toString();
    }

    /**
     * 设置回调接口
     */
    public void setLockPatternCallBack(LockPatternCallBack lockPatternCallBack) {
        this.lockPatternCallBack = lockPatternCallBack;
    }

    /**
     * 解锁回调接口
     */
    public interface LockPatternCallBack {
        void checkSuccess();

        void checkError();
    }

    /**
     * 点类,表示九宫格中的一个点
     */
    private class Point {
        float centerX; // 中心X坐标
        float centerY; // 中心Y坐标
        int index; // 索引
        private final int STATUS_NORMAL = 1; // 正常状态
        private final int STATUS_PRESS = 2; // 按下状态
        private final int STATUS_ERROR = 3; // 错误状态
        private int status = STATUS_NORMAL; // 当前状态

        public Point(float centerX, float centerY, int index) {
            this.centerX = centerX;
            this.centerY = centerY;
            this.index = index;
        }

        void setStatusPress() {
            status = STATUS_PRESS;
        }

        void setStatusNormal() {
            status = STATUS_NORMAL;
        }

        void setStatusError() {
            status = STATUS_ERROR;
        }
//是否按下状态是按状态
        boolean isPressStatus() {
            return status == STATUS_PRESS;
        }
//是正常状态
        boolean isNormalStatus() {
            return status == STATUS_NORMAL;
        }
//是否错误状态
        boolean isErrorStatus() {
            return status == STATUS_ERROR;
        }
    }
}




public class MathUtil {
    /**
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return
     */
    public static double distance(double x1, double y1, double x2, double y2) {
        return Math.sqrt(Math.abs(x1 - x2) * Math.abs(x1 - x2)
                + Math.abs(y1 - y2) * Math.abs(y1 - y2));
    }

    /**
     *
     * @param x
     * @param y
     * @return
     */
    public static double pointTotoDegrees(double x, double y) {
        return Math.toDegrees(Math.atan2(x, y));
    }

    public static boolean checkInRound(float sx, float sy, float r, float x,
                                       float y) {
        // x的平方 + y的平方 开根号 < 半径
        return Math.sqrt((sx - x) * (sx - x) + (sy - y) * (sy - y)) < r;
    }
}

效果

安卓9宫格密码键盘_第1张图片

你可能感兴趣的:(android)