天天看点

Android应用 手势密码的实现(二)

本文基于Hongyang大神的博客:http://blog.csdn.net/lmj623565791/article/details/36236113

转载请注明来源:http://blog.csdn.net/u013258802/article/details/53063691

上一篇界面修改的文章:http://blog.csdn.net/u013258802/article/details/52959513

本文的目的如下:

1、隐藏手势轨迹

2、自动重置

一、隐藏手势轨迹

1、先看 GestureLockView:

隐藏手势轨迹需要 GestureLockView 一直保持 STATUS_NO_FINGER 的绘制状态,除非用户输入错误。

所以我们就需要获取手势正误的状态以及知道是否绘制路径:

// 用于设置是否绘制轨迹(箭头和内圆)
private boolean showPath = true;// 默认显示

// 用于标记答案是否正确
private boolean isAnswerRight;
public void setIsAnswerRight(boolean isRight) {
    isAnswerRight = isRight;
}
           

OnDraw内 switch 块内的逻辑需要改变。

当 showPath 为 false 时,我们希望 FINGER_ON 的绘制代码绘制出 NO_FINGER 的样式,把 绘制代码和break 包在 if (showPath) {}  中即可实现这个效果。

当 showPath 为 false 并且 isAnswerRight 为 true 时,我们也希望 FINGER_UP 的绘制代码绘制出 NO_FINGER 的样式,那么同上。

根据上面的设计,我们必须保证标识最多的状态放在最上面执行,否则部分标识更新慢的话就会导致乱序,语言说明不便理解,我们下一步要更换原代码里 FINGER_UP 和 FINGER_ON 的执行顺序,想加深理解的可以暂时保持原先的顺序看看效果。

 流程图:

Android应用 手势密码的实现(二)

代码:

@Override
protected void onDraw(Canvas canvas)
{
    switch (mCurrentStatus)
    {
        case STATUS_FINGER_UP:
            // 在手指抬起后,答案错误一定会进行绘制,如果答案正确那么再判断是否显示路径
            if (!isAnswerRight || showPath) {
                // 绘制外圆
                mPaint.setColor(mColorFingerUp);
                mPaint.setStyle(Paint.Style.STROKE);
                mPaint.setStrokeWidth(mStrokeWidth);
                canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
                // 绘制内圆
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(mCenterX, mCenterY, mRadius * mInnerCircleRadiusRate, mPaint);
                // 绘制箭头
                drawArrow(canvas);

                break;
            }
        case STATUS_FINGER_ON:
            // 在画手势密码时判断是否显示路径
            if (showPath) {
                // 绘制外圆
                mPaint.setStyle(Paint.Style.STROKE);
                mPaint.setColor(mColorFingerOn);
                mPaint.setStrokeWidth(mStrokeWidth);
                canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
                // 绘制内圆
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(mCenterX, mCenterY, mRadius * mInnerCircleRadiusRate, mPaint);

                break;
            }
        case STATUS_NO_FINGER:
            // 绘制外圆
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(mColorNoFingerOutter);
            mPaint.setStrokeWidth(mStrokeWidth / 2);
            canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
            // 绘制内圆
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mColorNoFingerInner);
            canvas.drawCircle(mCenterX, mCenterY, mRadius * mInnerCircleRadiusRate, mPaint);

            break;
    }
}
           

我们需要在View的每次 onDraw 前更新 showPath 的状态,否则设置无法及时生效,因此现在要改 setMode 方法,增加一个 showPath 参数:

public void setMode(Mode mode, boolean showPath) {
    this.mCurrentStatus = mode;
    this.showPath = showPath;
    invalidate();
}
           

2、接下来就是GestureLockViewGroup:

首先添加变量和set方法:

// 记录答案是否正确
private boolean isAnswerRight = true;
// 默认显示轨迹
private boolean showPath = true;
// 对外公开的set方法
public void setShowPath(boolean showPath) {
    this.showPath = showPath;
}
           

然后调用setMode的地方就要增加参数了,这个就不多说了,例:

mGestureLockViews[i].setMode(GestureLockView.Mode.STATUS_NO_FINGER, showPath);
           

在手指抬起的事件中我们需要获取答案是否正确,然后在dispacthDraw()方法中依据标识状态避开路径的绘制。

ACTION_UP事件部分(获取 isAnswerRight 的状态):

// 回调是否成功
if (mOnGestureLockViewListener != null && mChoose.size() > 0) {
    isAnswerRight = checkAnswer();// 获取状态
    setViewColor(isAnswerRight);
    mOnGestureLockViewListener.onGestureEvent(isAnswerRight);
    if (this.mTryTimes == 0) {
        ...
    }
           

DispatchDraw方法部分(ShowPath为 false 并且答案为 true 时不进行绘制,和流程图一致):

@Override
public void dispatchDraw(Canvas canvas)
{
    super.dispatchDraw(canvas);
    if (!showPath && isAnswerRight) {
        return;
    }
    //绘制GestureLockView间的连线
    if (mPath != null)
    {...
           

当然这里还有点小问题,就是在画手势(ACTION_MOVE)过程中,会因为上一次的错误答案导致 isAnswerRight 为 false 并进入绘制流程,所以需要在reset时把 isAnswerRight 初始化为 true:

private void reset()
{
    mChoose.clear();
    mPath.reset();
    isAnswerRight = true;// 重置
    for (GestureLockView gestureLockView : mGestureLockViews)
    {
        gestureLockView.setMode(GestureLockView.Mode.STATUS_NO_FINGER, showPath);
        gestureLockView.setArrowDegree(-1);
    }
}
           

最后我们在changeItemMode 方法中调用之前为GestureLockView 添加的 setIsAnswerRight() 方法:

private void changeItemMode()
{
    for (GestureLockView gestureLockView : mGestureLockViews)
    {
        if (mChoose.contains(gestureLockView.getId()))
        {
            gestureLockView.setViewColor(mFingerUpColor);
            gestureLockView.setIsAnswerRight(isAnswerRight);// 调用
            gestureLockView.setMode(GestureLockView.Mode.STATUS_FINGER_UP, showPath);
        }
    }
}
           

好了,现在在Activity中调用setShowPath()方法:

mGesture = (GestureLockViewGroup) findViewById(R.id.gesture_lock_view_group);
mGesture.setAnswer(...);
mGesture.setShowPath(false);// 调用
           

运行结果,只有错误时才显示路径:

Android应用 手势密码的实现(二)
Android应用 手势密码的实现(二)

二、自动重置

GestureLockViewGroup的 ACTION_DOWN 触摸事件里用到了reset(),可见落指时页面内容会重置,但我希望画完手势过一秒左右自动清空,所以写个方法delayReset(),delayReset()会延时1000ms执行reset()。

private Handler mHandler = new Handler();
private Runnable mRunnable = new Runnable() {
    @Override
    public void run() {
        reset();
        invalidate();
    }
};
private void delayReset() {
    mHandler.postDelayed(mRunnable, 1000);// 加入线程队列
}
           

这里需要留心在延时的时候用户点击界面的情况,所以在 reset() 方法执行时,如果存在延时操作则把延时的操作取消:

private void reset()
{
    if (mHandler != null && mRunnable != null) {
        mHandler.removeCallbacks(mRunnable);// 从线程队列中移除
    }
    mChoose.clear();
    mPath.reset();
    ...
           

然后在 ACTION_UP 里 break 之前调用:

case MotionEvent.ACTION_UP:
    ...
    // 计算每个元素中箭头需要旋转的角度
    for (...)
    {
        ...
    }
    delayReset();// 调用
    break;
           

OK,现在可以看看自动重置的效果了:

Android应用 手势密码的实现(二)

三、结:

这篇文章就到这里了,下一篇文章会做最后的改动,主要是增加一个功能,然后点击事件的逻辑需要做些修改:

1、加一个设置初始密码的功能

2、让手势单点可以生效

3、自动补充轨迹(例如选中第一排的1位和3位时2位也能自动选中)