手记

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

相信大家在很多的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的解锁Viewprivate 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;
}@Overrideprotected 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;@Overridepublic 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-,(注意微信号最后面有个减号)。


0人推荐
随时随地看视频
慕课网APP