Android(安卓)图案解锁
16lz
2021-01-24
首先还是看效果图。
图案解锁的功能在许多应用中都有用过,它比起数字解锁,带给用户的体验要好,今天就来一步一步实现这个功能。
一、初始化
初始化放在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 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 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 pressedPoints = new ArrayList<>(); private ArrayList 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 passList); } private OnDrawFinishedListener onDrawFinishedListener; public void setOnDrawFinishedListener(OnDrawFinishedListener onDrawFinishedListener) { this.onDrawFinishedListener = onDrawFinishedListener; }}
欢迎关注公众号。
更多相关文章
- Android(安卓)OTA本地自动升级实现
- Android(安卓)Webview 开发详解
- Android(安卓)第八天_重置版_服务_注意事项
- android adb网络连接方法
- LoaderManager - Android(安卓)3.0新特性
- Android之Adapter使用方法总结
- Android布局优化的几种方法
- 应用程序基础(Application Fundamentals)
- Android中的自定义Adapter(继承自BaseAdapter)——与系统Adapter的