首先还是看效果图。

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

一、初始化

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

欢迎关注公众号。

更多相关文章

  1. Android(安卓)OTA本地自动升级实现
  2. Android(安卓)Webview 开发详解
  3. Android(安卓)第八天_重置版_服务_注意事项
  4. android adb网络连接方法
  5. LoaderManager - Android(安卓)3.0新特性
  6. Android之Adapter使用方法总结
  7. Android布局优化的几种方法
  8. 应用程序基础(Application Fundamentals)
  9. Android中的自定义Adapter(继承自BaseAdapter)——与系统Adapter的

随机推荐

  1. PHP生成器-动态生成内容的数组
  2. PHP7中创建COOKIE和销毁COOKIE的方法
  3. PHP中设置session过期的方法
  4. php实现将文件上传到临时目录
  5. PHP7中创建session和销毁session的方法
  6. PHP底层分析之关于强制分裂
  7. PHP 枚举类型的管理与设计
  8. PHP中mysqli_get_server_version()的用法
  9. Centos下PHP5升级为PHP7的方法
  10. PHP脚本实现Markdown文章上传到七牛图床