Preface: Two days ago, because of the project, I did a digital unlocking function imitating ios, and then I wrote that article. Lock your app (android digital unlock) Later, I came to think that there is another common unlock in the application, which is to draw pattern unlock. The layout of the two unlocks looks very similar, and the results are very similar, but the user's operation is different. Let me explain to you below.
Let's not say much. Firstly, the picture above:
Normal condition
Press state
Lift up the wrong state
Lift the correct state
thinking
Here is another Nine-palace layout, which can be referred to in the previous article. Lock your app (android digital unlock) It's just that we're drawing bitmaps on the nine palaces here. In the onDraw method, we need to draw two things, one is a point, the other is a line. We will not say much about drawing points. According to coordinates, we can draw a circular picture.
Now let's draw the line:
1. First, we need to get the set of pressing points:
We can use collections to save the point in the nine palaces when the touch event is pressed.
2. Then each two points are connected into a line.
First of all, we need to determine whether the state of the first point is normal (this is the attribute of the point, which can be customized). If it is normal, there will be a correct line between the two points, and if it is wrong, there will be a wrong line between the two points.
After the layout is drawn, we also need to judge the gesture, that is, the onTouch event, press, move and lift.
1. Press:
(1) Operation before clearing, a new round of drawing pattern begins (2) Check whether the point currently pressed coincides with the point in the Nine Palaces. If the coincidence occurs, it will be judged whether the point in the Nine Palaces is selected for the first time.
2. Mobile:
(1) Judging whether the point in the Nine Palaces is selected by the first press (2) If the point in the Nine Palace is selected for the first time, the mark position of the point that the finger is moving and the point that the finger is pressing is not the point in the Nine Palace is true.
3. Lift up:
(1). Restore all identification bits to initialization
End of drawing:
1. Judging whether the drawing is valid first
2. Then send callbacks to the interface based on the results of the drawing.
At this point, the relevant analysis is over, and I will give a detailed explanation in the code:
public class GraphicLockView extends View { private Point[][] points = new Point[3][3]; //Create a point array of three rows and three columns private boolean isInit; //Determine whether there is initialization private boolean isSelect; //Determine if the finger first clicks on the screen has a selected point private boolean isFinishMove; //Represents the end of a complete pattern drawing private boolean isMoveButNotPoint; //It means that the fingers are moving, but it's not the point in the ninth grid. private float width, height; //Screen width and height private static final int MIN_POINT = 4; //Minimum number of points that can constitute a password private float offsetsX, offsetsY; //Offset (where the offset is equal to the large side minus the small side and divides by 2) private float bitmapR; //Radius of Picture Resources private float moveX, moveY; //x,y coordinates of gesture movement private Bitmap bpPointNormal, bpPointPressed, bpPointError; //Three Pictures of Points private Bitmap bpLinePressed, bpLineError; //Three Pictures of Line private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private List<Point> selectPointList = new ArrayList<>(); //Store the set of pressed points private Matrix matrix = new Matrix(); //Matrix to handle line scaling private OnGraphicLockListener onGraphicLockListener; //External Monitor public GraphicLockView(Context context) { super(context); } public GraphicLockView(Context context, AttributeSet attrs) { super(context, attrs); } public GraphicLockView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void setOnGraphicLockListener(OnGraphicLockListener onGraphicLockListener) { this.onGraphicLockListener = onGraphicLockListener; } @Override protected void onDraw(Canvas canvas) { //Initialize a point before drawing, so you must first determine whether it has been initialized or not. if (!isInit) { //Initialization point initPoints(); } //Drawing - Drawing points onto canvas pointToCanvas(canvas); if (selectPointList.size() > 0) { Point startPoint = selectPointList.get(0); //Draw the points in the nine-grid coordinates for (int i = 0; i < selectPointList.size(); i++) { Point endPoint = selectPointList.get(i); lineToCanvas(canvas, startPoint, endPoint); startPoint = endPoint; } //Drawing Points Beyond Nine-Palace Coordinates if (isMoveButNotPoint) { lineToCanvas(canvas, startPoint, new Point(moveX, moveY)); } } } /** * Initialization point */ private void initPoints() { //1. Get the width and height of the canvas first (the width and height of the screen) width = getWidth(); height = getHeight(); /*================================================================================*/ //2. Judging the horizontal and vertical screen and calculating the offset if (width > height) { //Horizontal screen //Only x coordinates have offset in horizontal screen offsetsX = (width - height) / 2; /** * Think of the screen as a square (because the nine palaces are square, which is better to calculate here), with the smallest side as the benchmark. */ width = height; } else { //Vertical screen //When the screen is vertical, only y coordinates have offset. offsetsY = (height - width) / 2; height = width; } /*================================================================================*/ //3. Picture resources (Picture resources added by themselves) bpPointNormal = BitmapFactory.decodeResource(getResources(), R.drawable.point_normal); bpPointPressed = BitmapFactory.decodeResource(getResources(), R.drawable.point_pressed); bpPointError = BitmapFactory.decodeResource(getResources(), R.drawable.point_error); bpLinePressed = BitmapFactory.decodeResource(getResources(), R.drawable.line_pressed); bpLineError = BitmapFactory.decodeResource(getResources(), R.drawable.line_error); /*================================================================================*/ //4. Point coordinates //First row points[0][0] = new Point(offsetsX + width / 4, offsetsY + height / 4); points[0][1] = new Point(offsetsX + width / 2, offsetsY + height / 4); points[0][2] = new Point(offsetsX + width - width / 4, offsetsY + height / 4); //The second row points[1][0] = new Point(offsetsX + width / 4, offsetsY + height / 2); points[1][1] = new Point(offsetsX + width / 2, offsetsY + height / 2); points[1][2] = new Point(offsetsX + width - width / 4, offsetsY + height / 2); //The third row points[2][0] = new Point(offsetsX + width / 4, offsetsY + height - height / 4); points[2][1] = new Point(offsetsX + width / 2, offsetsY + height - height / 4); points[2][2] = new Point(offsetsX + width - width / 4, offsetsY + height - height / 4); /*================================================================================*/ //5. Calculating the radius of image resources bitmapR = bpPointNormal.getWidth() / 2; /*================================================================================*/ //6. Set password keys, initialize each point, set to 1-9 int index = 1; for (int i = 0; i < points.length; i++) { for (int j = 0; j < points[i].length; j++) { points[i][j].index = index; index++; } } /*================================================================================*/ //Initialization complete isInit = true; } @Override public boolean onTouchEvent(MotionEvent event) { moveX = event.getX(); moveY = event.getY(); isFinishMove = false; isMoveButNotPoint = false; Point point = null; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //Every time a finger is pressed, it means redrawing the pattern. resetPoint(); //1. Check whether the points currently pressed coincide with the nine points in the Nine Palaces. point = checkSelectPoint(); if (point != null) { isSelect = true; } break; case MotionEvent.ACTION_MOVE: if (isSelect) { point = checkSelectPoint(); if (point == null) { isMoveButNotPoint = true; } } break; case MotionEvent.ACTION_UP: isFinishMove = true; isSelect = false; break; } //Repeated checks in selection if (!isFinishMove && isSelect && point != null) { if (checkCrossPoint(point)) { //Intersection isMoveButNotPoint = true; } else { //Non-intersection (new point) point.status = Point.STATE_PRESSED; selectPointList.add(point); } } //End of drawing if (isFinishMove) { //Drawing does not hold water if (selectPointList.size() == 1) { resetPoint(); //Drawing errors, not enough points } else if (selectPointList.size() < MIN_POINT && selectPointList.size() > 0) { if (null != onGraphicLockListener) { onGraphicLockListener.setPwdFailure(); } errorPoint(); //Successful rendering } else { if (null != onGraphicLockListener) { String strPassword = ""; for (Point pwdPoint : selectPointList) { strPassword += pwdPoint.index; } if (!TextUtils.isEmpty(strPassword)) { onGraphicLockListener.setPwdSuccess(strPassword); } correctPoint(); } } } //Refreshing view calls onDraw method postInvalidate(); return true; } /** * Check the intersection * * @param point spot * @return Is it cross? */ private boolean checkCrossPoint(Point point) { if (selectPointList.contains(point)) { return true; } return false; } /** * Settings rendering is not valid */ public void resetPoint() { //Restore the state of a point for (Point point : selectPointList) { point.status = Point.STATE_NORMAL; } selectPointList.clear(); } /** * Set the drawing error to restore the state of the point */ public void errorPoint() { for (Point point : selectPointList) { point.status = Point.STATE_ERROR; } new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } handler.sendEmptyMessage(0); } }).start(); } /** * Set the drawing to succeed and restore the state of the point */ private void correctPoint() { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } handler.sendEmptyMessage(0); } }).start(); } private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { for (Point point : selectPointList) { point.status = Point.STATE_NORMAL; } selectPointList.clear(); postInvalidate(); } }; private Point checkSelectPoint() { for (int i = 0; i < points.length; i++) { for (int j = 0; j < points[i].length; j++) { Point point = points[i][j]; if (AppUtil.isCoincide(point.x, point.y, bitmapR, moveX, moveY)) { return point; } } } return null; } /** * Draw points onto the canvas * * @param canvas canvas */ private void pointToCanvas(Canvas canvas) { //The Set of Ergodic Points for (int i = 0; i < points.length; i++) { for (int j = 0; j < points[i].length; j++) { Point point = points[i][j]; if (points[i][j].status == Point.STATE_PRESSED) { canvas.drawBitmap(bpPointPressed, point.x - bitmapR, point.y - bitmapR, mPaint); } else if (points[i][j].status == Point.STATE_ERROR) { canvas.drawBitmap(bpPointError, point.x - bitmapR, point.y - bitmapR, mPaint); } else { canvas.drawBitmap(bpPointNormal, point.x - bitmapR, point.y - bitmapR, mPaint); } } } } /** * Draw the line onto the canvas * * @param canvas canvas * @param startPoint Starting point * @param endPoint The end point */ private void lineToCanvas(Canvas canvas, Point startPoint, Point endPoint) { float lineLength = (float) AppUtil.twoPointDistance(startPoint, endPoint); float degree = AppUtil.getDegrees(startPoint, endPoint); canvas.rotate(degree, startPoint.x, startPoint.y); //rotate if (startPoint.status == Point.STATE_PRESSED) { //Pressed state //Set the zoom ratio of the line, where the line is zoomed in one direction, that is, the x-axis, we only need to set the zoom ratio of the x-axis, the y-axis defaults to 1. matrix.setScale(lineLength / bpLinePressed.getWidth(), 1); matrix.postTranslate(startPoint.x - bpLinePressed.getWidth() / 2, startPoint.y - bpLinePressed.getHeight() / 2); canvas.drawBitmap(bpLinePressed, matrix, mPaint); } else { //The state of error matrix.setScale(lineLength / bpLineError.getWidth(), 1); matrix.postTranslate(startPoint.x - bpLineError.getWidth() / 2, startPoint.y - bpLineError.getHeight() / 2); canvas.drawBitmap(bpLineError, matrix, mPaint); } canvas.rotate(-degree, startPoint.x, startPoint.y); //Turn the angle of rotation back. } /** * Pattern monitor */ public interface OnGraphicLockListener { void setPwdSuccess(String password); void setPwdFailure(); } }
Let's look at a wave of gif motions:
Github Download Address: Portal