首先还是看效果图。
图案解锁的功能在许多应用中都有用过,它比起数字解锁,带给用户的体验要好,今天就来一步一步实现这个功能。
一、初始化
初始化放在onDraw方法中,因为onDraw方法在绘制过程中会执行多次,我们设置一个标量isInit,使初始化只执行一次。
@Override
protected void onDraw(Canvas canvas) {
if (!isInit) {
init();
}
}
private void init() {
pressPaint.setColor(Color.YELLOW);
pressPaint.setStrokeWidth(10);
errorPaint.setColor(Color.RED);
errorPaint.setStrokeWidth(10);
int width = getWidth();
int height = getHeight();
int offset = Math.abs(width - height) / 2;
int offsetX, offsetY;
int space;
if (width > height) //横屏
{
space = height / 4;
offsetX = offset;
offsetY = 0;
} else {
space = width / 4;
offsetX = 0;
offsetY = offset;
}
points[0][0] = new Point(offsetX + space, offsetY + space);
points[0][1] = new Point(offsetX + space * 2, offsetY + space);
points[0][2] = new Point(offsetX + space * 3, offsetY + space);
points[1][0] = new Point(offsetX + space, offsetY + space * 2);
points[1][1] = new Point(offsetX + space * 2, offsetY + space * 2);
points[1][2] = new Point(offsetX + space * 3, offsetY + space * 2);
points[2][0] = new Point(offsetX + space, offsetY + space * 3);
points[2][1] = new Point(offsetX + space * 2, offsetY + space * 3);
points[2][2] = new Point(offsetX + space * 3, offsetY + space * 3);
normalBitap = BitmapFactory.decodeResource(getResources(), R.drawable.normal);
pressBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.press);
errorBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.error);
bitmapR = normalBitap.getWidth() / 2;
isInit = true;
}
二、绘制九个点
绘制前我们先需要确定这九个点的位置,为了适应手机屏的横竖屏切换,我们把这九个点的位置控制在屏幕最短长度范围内,也就是竖屏时我们以手机宽度为准,横屏时我们以手机的高度为准。然后以这个长度确定一个正方形区域,在这个正方形区域内画出这九个点并均匀分布,点的位置已在上面的初始化工作中计算好了,下面就直接绘制了。
private void drawPoint(Canvas canvas) {
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[1].length; j++) {
Point point = points[i][j];
if (point.state == Point.STATE_NORMAL) {
canvas.drawBitmap(normalBitap, point.x - bitmapR, point.y - bitmapR, pointPaint);
} else if (point.state == Point.STATE_PRESS) {
canvas.drawBitmap(pressBitmap, point.x - bitmapR, point.y - bitmapR, pointPaint);
} else {
canvas.drawBitmap(errorBitmap, point.x - bitmapR, point.y - bitmapR, pointPaint);
}
}
}
}
这里需要注意画笔绘制的起始点和点所在的中心点有所偏差,所以代码中我们横纵坐标都减去了点的半径长度(bitmapR)。
三、记录手指的绘制
@Override
public boolean onTouchEvent(MotionEvent event) {
// super.onTouchEvent(event);
mouseX = event.getX();
mouseY = event.getY();
int[] ij;
int i, j;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
resetPoint();
ij = getSelectPoint();
if (ij != null) {
isDraw = true;
i = ij[0];
j = ij[1];
points[i][j].state = Point.STATE_PRESS;
pressedPoints.add(points[i][j]);
passList.add(i * points.length + j);
}
break;
case MotionEvent.ACTION_MOVE:
if (isDraw) {
ij = getSelectPoint();
if (ij != null) {
i = ij[0];
j = ij[1];
//同一个点不能再添加到pressedPoints中
if (!pressedPoints.contains(points[i][j])) {
points[i][j].state = Point.STATE_PRESS;
pressedPoints.add(points[i][j]);
passList.add(i * points.length + j);
}
}
}
break;
case MotionEvent.ACTION_UP:
isDraw = false;
break;
}
invalidate();
return true;
}
每次重新绘制前都重置所有数据
public void resetPoint() {
pressedPoints.clear();
passList.clear();
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[0].length; j++) {
points[i][j].state = Point.STATE_NORMAL;
}
}
invalidate();
}
判断手指划过的地方是否是图案中的某个点
private int[] getSelectPoint() {
Point pMouse = new Point(mouseX, mouseY);
System.out.println("bitmapR:" + bitmapR);
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[0].length; j++) {
if (points[i][j].distance(pMouse) < bitmapR) {
int[] result = new int[2];
result[0] = i;
result[1] = j;
return result;
}
}
}
return null;
}
返回的数组中记录点的坐标位置
手指滑动过程中,还需要不断的画线,此时onDraw方法是这样的:
@Override
protected void onDraw(Canvas canvas) {
if (!isInit) {
init();
}
//画点
drawPoint(canvas);
//有点后,开始画线
if (pressedPoints.size() > 0) {
Point a = pressedPoints.get(0);
for (int i = 0; i < pressedPoints.size(); i++) {
Point b = pressedPoints.get(i);
drawLine(canvas, a, b);
a = b;
}
if (isDraw) {
drawLine(canvas, a, new Point(mouseX, mouseY));
}
}
}
画线的方法:
private void drawLine(Canvas canvas, Point a, Point b) {
if (a.state == Point.STATE_PRESS) {
canvas.drawLine(a.x, a.y, b.x, b.y, pressPaint);
} else if (a.state == Point.STATE_ERROR) {
canvas.drawLine(a.x, a.y, b.x, b.y, errorPaint);
}
}
四、添加回调监听接口
public interface OnDrawFinishedListener {
boolean onDrawFinished(List<Integer> passList);
}
private OnDrawFinishedListener onDrawFinishedListener;
public void setOnDrawFinishedListener(OnDrawFinishedListener onDrawFinishedListener) {
this.onDrawFinishedListener = onDrawFinishedListener;
}
我们在手势抬起时调用这个监听方法,onTouchEvent(MotionEvent event)方法中:
case MotionEvent.ACTION_UP:
boolean valid = false;
if (onDrawFinishedListener != null && isDraw) {
valid = onDrawFinishedListener.onDrawFinished(passList);
// onDrawFinishedListener.onDrawFinished(passList);
}
// if (passList.size() <= 3) {
// valid = false;
// }else {
// valid = true;
// }
if (!valid) {
for (Point p : pressedPoints) {
p.state = Point.STATE_ERROR;
}
}
isDraw = false;
break;
到这里就完成了图案解锁的自定义View。
在Activity中使用
GestureView myView = (GestureView) findViewById(R.id.view);
myView.setOnDrawFinishedListener(new GestureView.OnDrawFinishedListener() {
@Override
public boolean onDrawFinished(List<Integer> passList) {
boolean flag = false;
if (passList.size() <= 3) {
Toast.makeText(GestureActivity.this, "图案绘制有误!", Toast.LENGTH_SHORT).show();
flag = false;
}else {
Toast.makeText(GestureActivity.this, "图案绘制完成!", Toast.LENGTH_SHORT).show();
flag = true;
}
return flag;
}
});
最后附上GestureView的完整代码。
public class GestureView extends View {
private boolean isInit; //是否初始化
private Point[][] points = new Point[3][3];
private Paint pointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint pressPaint = new Paint();
private Paint errorPaint = new Paint();
private Bitmap normalBitap, pressBitmap, errorBitmap;
private int bitmapR;
private boolean isDraw;
private ArrayList<Point> pressedPoints = new ArrayList<>();
private ArrayList<Integer> passList = new ArrayList<>(); //记录所滑过的点的位置,第几个点,以便后续验证
public GestureView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
if (!isInit) {
init();
}
//画点
drawPoint(canvas);
//有点后,开始画线
if (pressedPoints.size() > 0) {
Point a = pressedPoints.get(0);
for (int i = 0; i < pressedPoints.size(); i++) {
Point b = pressedPoints.get(i);
drawLine(canvas, a, b);
a = b;
}
if (isDraw) {
drawLine(canvas, a, new Point(mouseX, mouseY));
}
}
}
private void drawLine(Canvas canvas, Point a, Point b) {
if (a.state == Point.STATE_PRESS) {
canvas.drawLine(a.x, a.y, b.x, b.y, pressPaint);
} else if (a.state == Point.STATE_ERROR) {
canvas.drawLine(a.x, a.y, b.x, b.y, errorPaint);
}
}
private void drawPoint(Canvas canvas) {
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[1].length; j++) {
Point point = points[i][j];
if (point.state == Point.STATE_NORMAL) {
canvas.drawBitmap(normalBitap, point.x - bitmapR, point.y - bitmapR, pointPaint);
} else if (point.state == Point.STATE_PRESS) {
canvas.drawBitmap(pressBitmap, point.x - bitmapR, point.y - bitmapR, pointPaint);
} else {
canvas.drawBitmap(errorBitmap, point.x - bitmapR, point.y - bitmapR, pointPaint);
}
}
}
}
public void resetPoint() {
pressedPoints.clear();
passList.clear();
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[0].length; j++) {
points[i][j].state = Point.STATE_NORMAL;
}
}
invalidate();
}
private void init() {
pressPaint.setColor(Color.YELLOW);
pressPaint.setStrokeWidth(10);
errorPaint.setColor(Color.RED);
errorPaint.setStrokeWidth(10);
int width = getWidth();
int height = getHeight();
int offset = Math.abs(width - height) / 2;
int offsetX, offsetY;
int space;
if (width > height) //横屏
{
space = height / 4;
offsetX = offset;
offsetY = 0;
} else {
space = width / 4;
offsetX = 0;
offsetY = offset;
}
points[0][0] = new Point(offsetX + space, offsetY + space);
points[0][1] = new Point(offsetX + space * 2, offsetY + space);
points[0][2] = new Point(offsetX + space * 3, offsetY + space);
points[1][0] = new Point(offsetX + space, offsetY + space * 2);
points[1][1] = new Point(offsetX + space * 2, offsetY + space * 2);
points[1][2] = new Point(offsetX + space * 3, offsetY + space * 2);
points[2][0] = new Point(offsetX + space, offsetY + space * 3);
points[2][1] = new Point(offsetX + space * 2, offsetY + space * 3);
points[2][2] = new Point(offsetX + space * 3, offsetY + space * 3);
normalBitap = BitmapFactory.decodeResource(getResources(), R.drawable.normal);
pressBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.press);
errorBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.error);
bitmapR = normalBitap.getWidth() / 2;
isInit = true;
}
float mouseX, mouseY;
@Override
public boolean onTouchEvent(MotionEvent event) {
// super.onTouchEvent(event);
mouseX = event.getX();
mouseY = event.getY();
int[] ij;
int i, j;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
resetPoint();
ij = getSelectPoint();
if (ij != null) {
isDraw = true;
i = ij[0];
j = ij[1];
points[i][j].state = Point.STATE_PRESS;
pressedPoints.add(points[i][j]);
passList.add(i * points.length + j);
}
break;
case MotionEvent.ACTION_MOVE:
if (isDraw) {
ij = getSelectPoint();
if (ij != null) {
i = ij[0];
j = ij[1];
if (!pressedPoints.contains(points[i][j])) {
points[i][j].state = Point.STATE_PRESS;
pressedPoints.add(points[i][j]);
passList.add(i * points.length + j);
}
}
}
break;
case MotionEvent.ACTION_UP:
boolean valid = false;
if (onDrawFinishedListener != null && isDraw) {
valid = onDrawFinishedListener.onDrawFinished(passList);
// onDrawFinishedListener.onDrawFinished(passList);
}
// if (passList.size() <= 3) {
// valid = false;
// }else {
// valid = true;
// }
if (!valid) {
for (Point p : pressedPoints) {
p.state = Point.STATE_ERROR;
}
}
isDraw = false;
break;
}
invalidate();
return true;
}
private int[] getSelectPoint() {
Point pMouse = new Point(mouseX, mouseY);
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[0].length; j++) {
if (points[i][j].distance(pMouse) < bitmapR) {
int[] result = new int[2];
result[0] = i;
result[1] = j;
return result;
}
}
}
return null;
}
public interface OnDrawFinishedListener {
boolean onDrawFinished(List<Integer> passList);
}
private OnDrawFinishedListener onDrawFinishedListener;
public void setOnDrawFinishedListener(OnDrawFinishedListener onDrawFinishedListener) {
this.onDrawFinishedListener = onDrawFinishedListener;
}
}