手把手教你写一个手势密码解锁View(GesturePasswordView)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhiwenyan/article/details/79652889

相信大家在很多的app肯定看到过手势密码解锁View,但是大家有没有想过怎么实现这样一个View,哈,接下来,小编手把手教大家教写一个GesturePasswordView。

先看一张效果图
2018-03-22_12_06_12.gif

要实现这样一个效果,首先需要在屏幕上绘制一个3x3九宫图,如下图
nine.png

具体思路:
1、要知道每个点在屏幕上位置。
2、知道各个点的位置,在去绘制,调用drawCircle(float cx, float cy, float radius, @NonNull Paint paint)方法。*

定义一个Point类,记录下每个点的位置和状态。

    public class Point {
    //点的圆心的x,y的位置
    public float centerX;
    public float centerY;
    //每个点的索引
    private int index;
    //正常的状态
    private int normalState = 0;
    //按下的状态
    private int pressState = 1;
    //错误的状态
    private int errorState = 2;
    private int state = normalState;
    public Point(float centerX, float centerY, int index) {
        this.centerX = centerX;
        this.centerY = centerY;
        this.index = index;
    }
    public void setErrorState() {
        this.state = errorState;
    }
    public void setPressState() {
        this.state = pressState;
    }
    public void setNormalState() {
        this.state = normalState;
    }
    public void setState(int state) {
        this.state = state;
    }
    public boolean stateIsPress() {
        return state == pressState;
    }
    public boolean stateIsNormal() {
        return state == normalState;
    }
    public boolean stateIsError() {
        return state == errorState;
    }

GesturePasswordView类

    public class GesturePasswordView extends View {
    //3x3的解锁View
    private static final int mRow = 3;
    private static final int mColumn = 3;
    //颜色
    private int mNormalColor = Color.GRAY;
    private int mPressedColor = Color.BLUE;
    private int mErrorColor = Color.RED;
    //画笔
    private Paint mNormalPaint;
    private Paint mPressPaint;
    private Paint mErrorPaint;
    private Paint mLinePaint;
    //保存Point的二位数组
    private Point[][] mPoints = new Point[3][3];
    private float mDotRadius;
    //被选中的点
    private List<Point> mSelectPoints = new ArrayList<>();
    private boolean mInitOnce;
    private boolean mIsTouchPoint;
    private boolean mIsErrorStatus;
    private GesturePasswordViewListener mGesturePasswordViewListener;

    public GesturePasswordView(Context context) {
        this(context, null);
    }

    public GesturePasswordView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GesturePasswordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void initPaint() {
        mNormalPaint = getPaint();
        mNormalPaint.setColor(mNormalColor);
        mPressPaint = getPaint();
        mPressPaint.setColor(mPressedColor);
        mErrorPaint = getPaint();
        mErrorPaint.setColor(mErrorColor);
        mLinePaint = getPaint();
        mLinePaint.setColor(mPressedColor);

    }

    /**
     * 画笔
     *
     * @return
     */
    private Paint getPaint() {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(mDotRadius / 9);
        return paint;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!mInitOnce) {
            initDot();
            initPaint();
            mInitOnce = true;
        }
        for (int i = 0; i < mPoints.length; i++) {
            for (int j = 0; j < mPoints[i].length; j++) {
                Point point = mPoints[i][j];
                if (point.stateIsNormal()) {
                    //先绘制外圆
                    canvas.drawCircle(point.centerX, point.centerY, mDotRadius, mNormalPaint);
                    //后绘制内圆
                    canvas.drawCircle(point.centerX, point.centerY, mDotRadius / 6, mNormalPaint);
                } else if (point.stateIsPress()) {
                    canvas.drawCircle(point.centerX, point.centerY, mDotRadius, mPressPaint);
                    canvas.drawCircle(point.centerX, point.centerY, mDotRadius / 6, mPressPaint);
                } else if (point.stateIsError()) {
                    //设置下线条画笔的颜色
                    canvas.drawCircle(point.centerX, point.centerY, mDotRadius, mErrorPaint);
                    canvas.drawCircle(point.centerX, point.centerY, mDotRadius / 6, mErrorPaint);
                }
            }
        }
        //绘制两个点之间的连线
        drawLineToCanvas(canvas);
    }

     /**
       * 初始化每个点
       */

    private void initDot() {
    int width = this.getWidth();
    int height = this.getHeight();
    int offsetX = 0;
    int offsetY = 0;
    //兼容下横竖屏
    if (height > width) {
      offsetY = (height - width) / 2;
     } else {
      offsetX = (width - height) / 2;
     }
    int squareWidth = width / 3;
     //外圆的半径
     mDotRadius = width / 12;
    //mPoints[0][0] = new Point(offsetX + squareWidth / 2, offsetY + squareWidth / 2, 0);
    //mPoints[0][1] = new Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth / 2, 1);
    //mPoints[0][2] = new Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth / 2, 2);
    //mPoints[1][0] = new Point(offsetX + squareWidth / 2, offsetY + squareWidth * 3 / 2, 3);
    //mPoints[1][1] = new Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth * 3 / 2, 4);
    //mPoints[1][2] = new Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth * 3 / 2, 5);
    //mPoints[2][0] = new Point(offsetX + squareWidth / 2, offsetY + squareWidth * 5 / 2, 6);
    //mPoints[2][1] = new Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth * 5 / 2, 7);
    //mPoints[2][2] = new Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth * 5 / 2, 8);
    //为了简便,用for循环
            for (int i = 0; i < mRow; i++) {
                for (int j = 0; j < mColumn; j++) {
                    mPoints[i][j] = new Point(offsetX + squareWidth * (j * 2 + 1) / 2,
                     offsetY + squareWidth * (i * 2 + 1) / 2, i * mPoints.length + j);
                }
            }
        }

写到这里3x3九宫图绘制完了,接下来是手指触摸时绘制两个点之间的连线了。如下图

CIJ6_4AYF8Y6YEI3PUOHS7B.png

两个点之间连线是内圆之外与内圆之外的连线,怎么计算这两个点的位置呢?如下图

m9.png

    private void drawLineToCanvas(Canvas canvas) {
        if (mSelectPoints.size() >= 1) {
            Point lastPoint = mSelectPoints.get(0);
            for (int i = 1; i < mSelectPoints.size(); i++) {
                drawLine(canvas, lastPoint, mSelectPoints.get(i));
                lastPoint = mSelectPoints.get(i);
            }
            //触摸的时候绘制
            if (mIsTouchPoint) {
                drawLine(canvas, lastPoint, new Point(mMovingX, mMovingY, -1));
            }
        }
    }
    /**
     * 绘制两个点之间的连线
     *
     * @param canvas
     * @param start
     * @param end
     */

  private void drawLine(Canvas canvas, Point start, Point end) {
        //两点之间的距离
        double pointDistance = MathUtil.distance(end.centerX, end.centerY, start.centerX, start.centerY);
        float dx = end.centerX - start.centerX;
        float dy = end.centerY - start.centerY;
        float rx = (float) ((dx / pointDistance) * (mDotRadius / 6));
        float ry = (float) ((dy / pointDistance) * (mDotRadius / 6));
        canvas.drawLine(start.centerX + rx, start.centerY + ry,
                end.centerX - rx, end.centerY - ry, mLinePaint);
    }

处理下手指的Touch事件

    float mMovingX;
    float mMovingY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //显示错误时有个时间,是错误的状态是手指触摸是不能绘制的
        if (mIsErrorStatus) {
            return false;
        }
        mMovingX = event.getX();
        mMovingY = event.getY();
        Point point = getPressPoint();
        switch (event.getAction()) {
            //手指按下
            case MotionEvent.ACTION_DOWN:
                if (point != null) {
                    mIsTouchPoint = true;
                    mSelectPoints.add(point);
                    point.setPressState();
                }
                break;
            //手指移动
            case MotionEvent.ACTION_MOVE:
                if (point != null) {
                    if (!mSelectPoints.contains(point)) {
                        mSelectPoints.add(point);
                        point.setPressState();
                    }
                }
                break;
            //手指抬起
            case MotionEvent.ACTION_UP:
                mIsTouchPoint = false;
                if (mGesturePasswordViewListener != null) {
                    if (mSelectPoints.size() < 4) {
                        showSelectError();
                    } else {
                        clearSelectPoints();
                    }
                }
                break;

        }
        invalidate();
        return true;
    }

用户可能会解锁错误,处理下解锁View错误的情况,每个点和点与点之间的连线显示红色

  /**
     * 显示错误
     */
    private void showSelectError() {
        for (int i = 0; i < mPoints.length; i++) {
            for (int j = 0; j < mPoints[i].length; j++) {
                Point point = mPoints[i][j];
                //把所有选中的点的状态设置为Error
                if (mSelectPoints.contains(point)) {
                    point.setErrorState();
                    mIsErrorStatus = true;
                    mLinePaint.setColor(mErrorColor);
                }
            }
        }
        postDelayed(new Runnable() {
            @Override
            public void run() {
                clearSelectPoints();
                mIsErrorStatus = false;
                invalidate();
                mLinePaint.setColor(mPressedColor);
            }
        }, 1000);

    }

显示错误完毕之后,需要恢复下点的正常状态

    /**
     * 清空所有选中的点
     */
    private void clearSelectPoints() {
        for (int i = 0; i < mPoints.length; i++) {
            for (int j = 0; j < mPoints[i].length; j++) {
                Point point = mPoints[i][j];
                //把所有选中的点的状态设置为Normal
                if (mSelectPoints.contains(point)) {
                    point.setNormalState();
                }
            }
        }
        mSelectPoints.clear();
    }

获取手指触摸的是哪个点,根据点的圆心位置到手指触摸的位置的距离小于外圆的半径。如图

m6.png

只要在蓝色区域内就可以,之外的话手指肯定不在这个圆内。

private Point getPressPoint() {
    for (int i = 0; i < mPoints.length; i++) {
        for (int j = 0; j < mPoints[i].length; j++) {
            Point point = mPoints[i][j];
            if (point != null) {
                if (MathUtil.checkInRound(point.centerX, point.centerY, mDotRadius, mMovingX, mMovingY)) {
                    return point;
                }
            }
        }
    }
    return null;
}

最后说下index的作用,index是记录了每个的点的索引,这样做可以知道用户连了哪些点。整个3x3的九宫图解锁到这绘制完毕,只粘贴了部分关键代码,完整代码github地址:https://github.com/StevenYan88/GesturePasswordView

有不懂的还可以加小编的微信StevenInSH-,(注意微信号最后面有个减号)。

猜你喜欢

转载自blog.csdn.net/zhiwenyan/article/details/79652889