首先还是看效果图。

android解锁action android解锁方式比例_图案解锁

android解锁action android解锁方式比例_i++_02


图案解锁的功能在许多应用中都有用过,它比起数字解锁,带给用户的体验要好,今天就来一步一步实现这个功能。

一、初始化

初始化放在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;
    }
}