自定义组件
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;
}
}
效果