安卓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<Point> 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;
    }
}

效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/374503.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

adb push 将电脑中的文件传输到安卓开发板

1. adb remount 重新挂载设备的文件系统&#xff0c;以便可以对设备进行读写操作&#xff0c;通常情况下&#xff0c;安卓开发板在连接到计算机后&#xff0c;设备的文件系统会被挂载为只读文件系统&#xff0c;重新挂载后变成可读可写权限 C:\Users\Administrator>adb re…

工业笔记本丨行业三防笔记本丨亿道加固笔记本定制丨极端温度优势

工业笔记本是专为在恶劣环境条件下工作而设计的高度耐用的计算机设备。与传统消费者级笔记本电脑相比&#xff0c;工业笔记本在极端温度下展现出了许多优势。本文将探讨工业笔记本在极端温度环境中的表现&#xff0c;并介绍其优势。 耐高温性能: 工业笔记本具有更高的耐高温性…

2月6日作业

1.现有无序序列数组为23,24,12,5,33,5347&#xff0c;请使用以下排序实现编程 函数1:请使用冒泡排序实现升序排序 函数2:请使用简单选择排序实现升序排序 函数3:请使用快速排序实现升序排序 函数4:请使用插入排序实现升序排序 #include<stdio.h> #include<string.h&…

2024-01-06-AI 大模型全栈工程师 - 机器学习基础

摘要 2024-01-06 阴 杭州 晴 本节简介: a. 数学模型&算法名词相关概念; b. 学会数学建模相关知识&#xff1b; c. 学会自我思考&#xff0c;提升认知&#xff0c;不要只会模仿&#xff1b; 课程内容 1. Fine-Tuning 有什么作用&#xff1f; a. 什么是模型训练&#xff…

计算机毕业设计 基于SpringBoot的线上教育培训办公系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

掌握Pandas数据转换利器深入解析pd.to_numeric函数与实战技巧【第63篇—python:Pandas数据】

文章目录 引言pd.to_numeric函数简介参数详解实战案例进阶应用&#xff1a;处理缺失值与异常值1. 处理缺失值2. 处理异常值 高效利用downcast参数优化内存占用优化性能&#xff1a;使用apply函数批量处理数据实战案例&#xff1a;处理时间序列数据处理多列数据&#xff1a;结合…

知识管理平台有哪些?帮助企业高效发展

在现代商业环境中&#xff0c;知识被认为是推动企业高效发展的关键因素。一个有效的知识管理平台可以帮助企业收集、整理和分享知识&#xff0c;从而提高工作效率&#xff0c;增强竞争优势。接下来&#xff0c;我将向大家推荐三款优秀的知识管理平台&#xff1a;Helplook&#…

Python初学者学习记录——python基础综合案例:数据可视化——动态柱状图

一、案例效果 通过pyecharts可以实现数据的动态显示&#xff0c;直观的感受1960~2019年世界各国GDP的变化趋势 二、通过Bar构建基础柱状图 反转x轴和y轴 标签数值在右侧 from pyecharts.charts import Bar from pyecharts.options import LabelOpts# 构建柱状图对象 bar Bar()…

理解“无意义的”JavaScript特性

概要 JavaScript可能是世界上最流行的客户端语言&#xff0c;但它远非完美&#xff0c;也不是没有怪癖。Juan Diego Rodriguez研究了几个“荒谬的”JavaScript怪癖&#xff0c;并解释了它们是如何进入语言的&#xff0c;以及如何在自己的代码中避免它们。 特性 为什么JavaS…

【并发编程】手写线程池阻塞队列

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程 ⛺️稳重求进&#xff0c;晒太阳 示意图 步骤1&#xff1a;自定义任务队列 变量定义 用Deque双端队列来承接任务用ReentrantLock 来做锁并声明两个条件变量 Condition fullWai…

PDF文件格式(一):交叉引用流

在PDF-1.5版本之前&#xff0c;对象的交叉引用信息是存储在交叉引用表(cross-reference table)中的。在PDF-1.5版本之后&#xff0c;引进了交叉引用流(cross-reference stream)对象&#xff0c;可以用它来存储对象的交叉引用信息&#xff0c;就像交叉引用表的功能一样。 采用交…

鸿蒙开发系列教程(十一)--布局应用:层叠布局

层叠布局 &#xff08;Stack&#xff09; 层叠布局&#xff08;StackLayout&#xff09;用于在屏幕上预留一块区域来显示组件中的元素&#xff0c;提供元素可以重叠的布局。层叠布局通过stack容器组件实现位置的固定定位与层叠&#xff0c;容器中的子元素&#xff08;子组件&…

Python进阶----在线翻译器(Python3的百度翻译爬虫)

目录 一、此处需要安装第三方库requests: 二、抓包分析及编写Python代码 1、打开百度翻译的官网进行抓包分析。 2、编写请求模块 3、输出我们想要的消息 三、所有代码如下&#xff1a; 一、此处需要安装第三方库requests: 在Pycharm平台终端或者命令提示符窗口中输入以下代…

JVM-JVM内存结构(二)

堆 堆(Heap) 通过new关键字&#xff0c;创建的对象都会使用堆内存特点&#xff1a; 他是线程共享的&#xff0c;堆中的对象需要考虑线程安全的问题有垃圾回收机制 堆内存溢出(OutOfMemoryError) 代码演示 List<String> list new ArrayList<>(); try{String …

【已解决】pt文件转onnx后再转rknn时得到推理图片出现大量锚框变花屏

前言 环境介绍&#xff1a; 1.编译环境 Ubuntu 18.04.5 LTS 2.RKNN版本 py3.8-rknn2-1.4.0 3.单板 迅为itop-3568开发板 一、现象 采用yolov5训练并将pt转换为onnx&#xff0c;再将onnx采用py3.8-rknn2-1.4.0推理转换为rknn&#xff0c;rknn模型能正常转换&#xff0c;…

阿里地址标准化相关能力

阿里云地址标准化服务入口 1地址标准化概念 阿地址标准化&#xff08;Address Purification&#xff09;是一站式闭环地址数据处理和服务平台产品&#xff0c;依托阿里云海量的地址语料库&#xff0c;针对各行业业务系统所登记的地址数据&#xff0c;进行纠错、补全、归一、结…

【QT】opcuaServer 的构建

【QT】opcuaServer 的构建 前言opcuaServer实现测试 前言 在博文【opcua】从编译文件到客户端的收发、断连、节点查询等实现 中&#xff0c;我们已经介绍了如何在QT 中创建opucaClient 。在本期的博文中&#xff0c;我们基于之前的部署环境&#xff0c;介绍一下如何构建opcuaS…

Windows 离线安装MySQL8教程

本文描述了在Windows 11 上离线安装MySQL8的方法本方法同样使用与Win10、Windows Server等本文仅供参考&#xff0c;请理解后安装和优化 一、下载文件 官方下载路径 https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.36-winx64.zip百度网盘缓存 微信小程序搜索“数字…

arcgis各种版本下载

arcgic 下载&#xff01;&#xff01;&#xff01; ArcGIS是一款地理信息系统软件&#xff0c;由美国Esri公司开发。它提供了一系列完整的GIS功能&#xff0c;包括地图制作、空间数据管理、空间分析、空间信息整合、发布与共享等。ArcGIS是一个可扩展的GIS平台&#xff0c;提供…

复杂人像背景分割解决方案

随着人工智能和图像处理技术的不断发展&#xff0c;人像处理已成为企业宣传、产品展示、线上教育等领域不可或缺的一环。然而&#xff0c;面对复杂多变的人像背景&#xff0c;如何实现精准、高效的分割&#xff0c;一直是困扰企业的技术难题。为此&#xff0c;美摄科技凭借其领…