参考自高仿支付宝解锁
构造
一个九宫格View + View的指示器标识已选单元
LockPatternView
构造函数以及成员变量
1 | private float movingX, movingY;//记录移动x,y |
主要类和成员方法
Cell类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Cell {
private int x;// the x position of circle's center point
private int y;// the y position of circle's center point
private int row;// the cell in which row
private int column;// the cell in which column
private int index;// the cell value(1-9)
private int status = STATE_NORMAL;//default status
//default status
public static final int STATE_NORMAL = 0;
//checked status
public static final int STATE_CHECK = 1;
//checked error status
public static final int STATE_CHECK_ERROR = 2;
public static final int STATE_PRESSED = 3;
...
}
手势密码状态1
2
3
4
5
6
7
8
9
10
11
12/**
* the display mode of the pattern
* 手势密码状态
*/
public enum DisplayMode {
//show default pattern (the default pattern is initialize status)
DEFAULT,
//show selected pattern normal
NORMAL,
//show selected pattern error
ERROR;
}
1 | /** |
九宫格中的每一个点的半径相当于 (宽度 - 外边距 )/ 8,为什么处以8呢?因为水平上有三个圆,每个圆有两条半径,共有6条,再加上三个圆中间两个间距。故8条,其余同理
onMeasure方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//必须重写onMeasured获得正确的宽高,再重绘
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
getMeasuredHeight();
this.width = getMeasuredWidth();
this.height = getMeasuredHeight();
if (width != height) {
throw new IllegalArgumentException("the width must be equals height");
}
this.initCellSize();
this.set9CellsSize();
this.invalidate();
}
/**
* set nine cells size
*/
private void set9CellsSize() {
int distance = this.cellBoxWidth + this.cellBoxWidth / 2 - this.cellRadius;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
mCells[i][j].setX(distance * j + cellRadius + offset);
mCells[i][j].setY(distance * i + cellRadius + offset);
}
}
}
绘制三角形1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46private void drawTriangle(Cell preCell, Cell nextCell, Canvas canvas, Paint paint) {
float distance = LockPatternUtil.getDistanceBetweenTwoPoints
(preCell.getX(), preCell.getY(), nextCell.getX(), nextCell.getY());
float x = this.cellInnerRadius * 2 / distance * (nextCell.getX() - preCell.getX()) + preCell.getX();//(2/3 * R)*cosθ + x
float y = this.cellInnerRadius * 2 / distance * (nextCell.getY() - preCell.getY()) + preCell.getY();
float angleX = LockPatternUtil.getAngleLineIntersectX(
preCell.getX(), preCell.getY(), nextCell.getX(), nextCell.getY(), distance);
float angleY = LockPatternUtil.getAngleLineIntersectY(
preCell.getX(), preCell.getY(), nextCell.getX(), nextCell.getY(), distance);
float x1, y1, x2, y2;
//slide right down
if (angleX >= 0 && angleX <= 90 && angleY >= 0 && angleY <= 90) {
x1 = x - (float) (cellInnerRadius * Math.cos(Math.toRadians(angleX - 30)));
y1 = y - (float) (cellInnerRadius * Math.sin(Math.toRadians(angleX - 30)));
x2 = x - (float) (cellInnerRadius * Math.sin(Math.toRadians(angleY - 30)));
y2 = y - (float) (cellInnerRadius * Math.cos(Math.toRadians(angleY - 30)));
}
//slide right up
else if (angleX >= 0 && angleX <= 90 && angleY > 90 && angleY <= 180) {
x1 = x - (float) (cellInnerRadius * Math.cos(Math.toRadians(angleX + 30)));
y1 = y + (float) (cellInnerRadius * Math.sin(Math.toRadians(angleX + 30)));
x2 = x - (float) (cellInnerRadius * Math.sin(Math.toRadians(180 - angleY + 30)));
y2 = y + (float) (cellInnerRadius * Math.cos(Math.toRadians(180 - angleY + 30)));
}
//slide left up
else if (angleX > 90 && angleX <= 180 && angleY >= 90 && angleY < 180) {
x1 = x + (float) (cellInnerRadius * Math.cos(Math.toRadians(180 - angleX - 30)));
y1 = y + (float) (cellInnerRadius * Math.sin(Math.toRadians(180 - angleX - 30)));
x2 = x + (float) (cellInnerRadius * Math.sin(Math.toRadians(180 - angleY - 30)));
y2 = y + (float) (cellInnerRadius * Math.cos(Math.toRadians(180 - angleY - 30)));
}
//slide left down
else {
x1 = x + (float) (cellInnerRadius * Math.cos(Math.toRadians(180 - angleX + 30)));
y1 = y - (float) (cellInnerRadius * Math.sin(Math.toRadians(180 - angleX + 30)));
x2 = x + (float) (cellInnerRadius * Math.sin(Math.toRadians(angleY + 30)));
y2 = y - (float) (cellInnerRadius * Math.cos(Math.toRadians(angleY + 30)));
}
trianglePath.reset();
trianglePath.moveTo(x, y);
trianglePath.lineTo(x1, y1);
trianglePath.lineTo(x2, y2);
trianglePath.close();
canvas.drawPath(trianglePath, paint);
}
- 先求出两点之间距离distance
- 定出三角形起始 x,y 。 x = float x = this.cellInnerRadius 2 / distance (nextCell.getX() - preCell.getX()) + preCell.getX();//(2/3 R) cosθ,θ为直线与x轴夹角。y同理
- 获得直线与x轴,y轴的夹角θ与δ,从而定出方向
- 根据方向,定出其余两个点
- 连接线段,绘制三角形
根据两点之间距离与半径关系判断是否选中某个点1
2
3
4
5
6
7
8
9
10
11private Cell checkSelectCell(float x, float y) {
for (int i = 0; i < mCells.length; i++) {
for (int j = 0; j < mCells[i].length; j++) {
Cell cell = mCells[i][j];
if (LockPatternUtil.checkInRound(cell.x, cell.y, this.cellRadius, x, y, -10)) {
return cell;
}
}
}
return null;
}
核心算法
在onTouchEvent中监听事件,进行对应处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21"ClickableViewAccessibility") (
public boolean onTouchEvent(MotionEvent event) {
getParent().requestDisallowInterceptTouchEvent(true);
float ex = event.getX();
float ey = event.getY();
if (!isLock) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleActionDown(ex, ey);
break;
case MotionEvent.ACTION_MOVE:
handleActionMove(ex, ey);
break;
case MotionEvent.ACTION_UP:
handleActionUp();
break;
}
}
return true;
}
down
1 | private void handleActionDown(float ex, float ey) { |
- 置标志位
- 设置当前模式为默认
- 回调监听器预处理
- 加入起始点
- 重绘
move
1 | private void handleActionMove(float ex, float ey) { |
- 置标志位
- 判断是否选中该点,是则加入该点
- 设置当前模式为常规状态并开始重绘
up
1 | private void handleActionUp() { |
重绘
每次设置模式后自动重绘,如果设置隐藏路径,即不重绘。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public void setPattern(DisplayMode mode) {
switch (mode) {
case DEFAULT:
for (Cell cell : sCells) {
cell.setStatus(Cell.STATE_NORMAL);
}
sCells.clear();
break;
case NORMAL:
break;
case ERROR:
for (Cell cell : sCells) {
cell.setStatus(Cell.STATE_CHECK_ERROR);
}
break;
}
this.handleStealthMode();
}
/**
* handle the stealth mode (if true: do not post invalidate; false: post invalidate)
*/
private void handleStealthMode() {
if (!mInStealthMode) {
this.postInvalidate();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.drawToCanvas(canvas);
}
private void drawToCanvas(Canvas canvas) {
for (int i = 0; i < mCells.length; i++) {
for (int j = 0; j < mCells[i].length; j++) {
switch (mCells[i][j].getStatus()) {
case Cell.STATE_CHECK: {
selectPaint.setStyle(Style.FILL);
selectPaint.setColor(getResources().getColor(R.color.color_red_fde7ec));
canvas.drawCircle(mCells[i][j].getX(), mCells[i][j].getY(),
this.cellRadius, this.selectPaint);
canvas.drawCircle(mCells[i][j].getX(), mCells[i][j].getY(),
this.cellRadius, this.defaultPaint);
selectPaint.setColor(getResources().getColor(R.color.pink));
canvas.drawCircle(mCells[i][j].getX(), mCells[i][j].getY(),
this.cellInnerRadius / 3, this.selectPaint);
}
break;
case Cell.STATE_NORMAL: {
canvas.drawCircle(mCells[i][j].getX(), mCells[i][j].getY(),
this.cellRadius, this.defaultPaint);
canvas.drawCircle(mCells[i][j].getX(), mCells[i][j].getY(),
this.cellInnerRadius / 3, this.selectPaint);
}
break;
case Cell.STATE_CHECK_ERROR: {
errorPaint.setStyle(Style.STROKE);
canvas.drawCircle(mCells[i][j].getX(), mCells[i][j].getY(),
this.cellRadius, this.errorPaint);
errorPaint.setStyle(Style.FILL);
canvas.drawCircle(mCells[i][j].getX(), mCells[i][j].getY(),
this.cellInnerRadius / 3, this.errorPaint);
}
break;
default:
break;
}
}
}
if (sCells.size() > 0) {
//temporary cell: at the beginning the cell is the first of sCells
Cell tempCell = sCells.get(0);
for (int i = 1; i < sCells.size(); i++) {
Cell cell = sCells.get(i);
if (cell.getStatus() == Cell.STATE_CHECK) {
drawLineIncludeCircle(tempCell, cell, canvas, selectPaint);
drawTriangle(tempCell, cell, canvas, selectPaint);
} else if (cell.getStatus() == Cell.STATE_CHECK_ERROR) {
drawLineIncludeCircle(tempCell, cell, canvas, errorPaint);
drawTriangle(tempCell, cell, canvas, selectPaint);
}
tempCell = cell;
}
if (isActionMove && !isActionUp) {
this.drawLineFollowFinger(tempCell, canvas, selectPaint);
}
}
}
- 根据mCells状态绘制点
- 按照sCells依次重绘线和三角形
- 若正在滑动,取出最后一个连接上的点与正常滑动的点划线
LockPatternIndicator
刷新状态,更新UI1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public void setIndicator(List<LockPatternView.Cell> cells) {
for(LockPatternView.Cell cell : cells) {
for(int i = 0; i < mIndicatorCells.length; i++) {
for(int j = 0; j < mIndicatorCells[i].length; j++) {
if (cell.getIndex() == mIndicatorCells[i][j].getIndex()) {
mIndicatorCells[i][j].setStatus(IndicatorCell.STATE_CHECK);
}
}
}
}
this.postInvalidate();
}
private void drawToCanvas(Canvas canvas) {
for(int i = 0; i < mIndicatorCells.length; i++) {
for(int j = 0; j < mIndicatorCells[i].length; j++) {
if(mIndicatorCells[i][j].getStatus() == IndicatorCell.STATE_NORMAL) {
canvas.drawCircle(mIndicatorCells[i][j].getX(), mIndicatorCells[i][j].getY(), radius, defaultPaint);
} else if(mIndicatorCells[i][j].getStatus() == IndicatorCell.STATE_CHECK) {
canvas.drawCircle(mIndicatorCells[i][j].getX(), mIndicatorCells[i][j].getY(), radius, selectPaint);
}
}
}
}
- 根据传入的Cells设置自己的mIndicatorCells状态
- 重绘