美文网首页Android开发Android开发Android技术知识
好用解耦的Pin码应用锁(附代码库)

好用解耦的Pin码应用锁(附代码库)

作者: af83084249b7 | 来源:发表于2018-05-21 12:53 被阅读315次

起因

各位小伙伴,开发中肯定会有应用锁的需求。手势锁也好、pin码锁也罢,亦或是指纹锁。不管哪一类,它们的基本逻辑都是差不多的,我这边以pin码锁为基础介绍实现方案,大家可根据需要拓展。

介绍

一:关于pin码锁这块,我做了两种基本样式,大家可以在此基础上修改。
二:里面有添加pin码、确认pin码、验证pin码、删除pin码四种动作。
三:同时,这边不仅实现了锁屏的需求,而且拓展了输入错误的次数统计处理。如果,错误超过3次(大家可修改次数),则会进入倒计时锁定状态(锁定时长可修改),期间无法进行输入验证。
四:错误次数、锁定状态,我这边做了状态保留,不管你是进入后台亦或杀掉应用重新启动。我这边都会恢复上次的状态。

效果

两种样式
circleStyle.png borderStyle.png
流程(添加、确认、验证、删除)
progress.gif
锁定状态(超过一定错误次数)
status.gif

上面图片大致介绍了,该项目包含的东西。样式、流程动作、以及锁定状态。样式方面可根据需要调整,完全无耦合。流程动作,用的全是同一个PinActivity实现。只需传递相关action参数即可。状态处理,可根据需要进行修改调整(次数、锁定时间)。无论从后台回复前台,亦或是杀掉后重新打开app,状态都会保留并更新。当锁定时间结束后,自动重置。

实现

关于启动页(刚打开蓝色页面为启动页),可参考我之前博客:https://www.jianshu.com/p/e18412b0977f
关于pin码样式(自定义输入框和自定义数字键盘),可参考我之前博客:https://www.jianshu.com/p/d2c6e6e59335
首先说明主题逻辑。

一:我们要判断应用是启动以及从后台进入前台。这个时候我们需要考虑pin码是否设置。
二:如果设置,则打开pin码Activity,并传递指定意图参数。意图参数分为四类:添加、确认(类似重复输入密码)、验证和删除。此时,应该传入验证。
三:由于应用启动后需要进入首页,所以我们需要追加意图参数,指定打开首页。如果是从后台进入前台,则认证完毕直接finish即可。无需打开任何页面,此时无需追加意图参数。在pinActivity认证完毕后,会根据参数处理不同逻辑。
三:对于pin码的添加:添加pin码时候指定意图类型为添加,然后打开。当输入完毕后,会启动新的pinActivity并指定意图参数为确认。此时,需要将添加页面参数传递过来,进行确认的一致匹配。
四:添加以及确认完毕之后,pin参数会被存储到本地,对于应用启动和进入前台则是根据该数据进行验证。
五:如果需要删除Pin码、则直接打开pinActivity并传递意图参数值为删除类型,当验证成功后,pin码本地数据会被清空。
六:对于pin码的验证,我代码逻辑里面添加了错误次数以及锁定事件。当错误超过指定次数则进入锁定状态,在锁定时间无法进行输入操作。当锁定时间用尽则会重置为可输入状态。
七:对于错误次数和锁定状态处理,代码添加了本地存储,为了保证用户杀掉app重新启动后逻辑依旧保留。对于这些数据,我是在生命周期的onResume、onPause进行的本地数据获取以及保存。同时,锁定时间,我采用的是倒计时器对象,及时刷新时间值,当倒计时结束则恢复可输入状态。倒计时处理在onResume、onPause方法做了刷新调整。如果onResume时候已经超过锁定时间,则直接恢复状态,无需再次刷新执行倒计时。

下面介绍重点代码。

应用启动

 在启动activiy添加如下逻辑
 private void pinWork() {
    Intent intent = null;
   //我这边是为了给大家更多样式选择,区分了两个pinActivity,实际开发大家用一个即可
    switch (PinUtil.getPinStyle()) {
        case PinUtil.PIN_BORDER_STYLE:
            intent = new Intent(this, BorderPinActivity.class);
            break;
        case PinUtil.PIN_CIRCLE_STYLE:
            intent = new Intent(this, CirclePinActivity.class);
            break;

    }
    if (intent == null) {
        return;
    }
    //指定pin意图参数类型为PinUtil.PIN_VERIFY(验证) 
    intent.putExtra(ConstantUtil.Intent.PIN_ACTION, PinUtil.PIN_VERIFY);
   //由于是第一次启动此时,验证完毕需要启动mainActivity,所以添加PIN_START_MAIN参数。
    intent.putExtra(ConstantUtil.Intent.PIN_START_MAIN, true);
    myStartActivity(intent);
    //关闭启动页
    finish();
   }

应用从后台进入前台

//implements Application.ActivityLifecycleCallbacks接口,
//获取应用的activity的生命周期监听(监听后台进入前台),非相关代码已删
public class BaseApp extends Application implements Application.ActivityLifecycleCallbacks {
   //根据mActivityCount  判断当前activity的数量(非stop状态)
   private int mActivityCount = -1;

@Override
public void onCreate() {
    super.onCreate();
   //注册生命周期监听
  registerActivityLifecycleCallbacks(this);
}

//该方法在activity start时候调用
@Override
public void onActivityStarted(Activity activity) {
    // 为了防止应用第一次启动触发该方法(我们在启动页面添加了相关逻辑)
   //mActivityCount  初始值置为-1
    if (mActivityCount == 0) {
        //应用从后台进入前台
        //后期逻辑跟启动activity类似
        pinWork();
    }
    if (mActivityCount == -1) {
        mActivityCount = 0;
    }
    mActivityCount++;
}


private void pinWork() {
    if (PinUtil.getPin() == null) {
        return;
    }
    switch (PinUtil.getPinStyle()) {
        case PinUtil.PIN_BORDER_STYLE:
            borderPinWork();
            break;
        case PinUtil.PIN_CIRCLE_STYLE:
            circlePinWork();
            break;
    }
}

private void circlePinWork() {
    //如果当前锁屏页面是否已在栈里面,如果有的话(比如,位于后台的栈顶),不再次启动锁屏activity
    Activity activity = ActivityManager.getInstance().getActivity(CirclePinActivity.class);
    if (activity != null) {
        return;
    }
    Activity currentActivity = ActivityManager.getInstance().currentActivity();
    if (currentActivity == null) {
        return;
    }
    Intent intent = new Intent(currentActivity, CirclePinActivity.class);
    intent.putExtra(ConstantUtil.Intent.PIN_ACTION, PinUtil.PIN_VERIFY);
    currentActivity.startActivity(intent);
    //取消切换动画,大家可根据需要修改
    currentActivity.overridePendingTransition(0,0);

}

private void borderPinWork() {
    //如果当前锁屏页面是否已在栈里面,如果有的话(比如,位于后台的栈顶),不再次启动锁屏activity
    Activity activity = ActivityManager.getInstance().getActivity(BorderPinActivity.class);
    if (activity != null) {
        return;
    }
    Activity currentActivity = ActivityManager.getInstance().currentActivity();
    if (currentActivity == null) {
        return;
    }
    Intent intent = new Intent(currentActivity, BorderPinActivity.class);
    intent.putExtra(ConstantUtil.Intent.PIN_ACTION, PinUtil.PIN_VERIFY);
    currentActivity.startActivity(intent);
    //取消切换动画,大家可根据需要修改
    currentActivity.overridePendingTransition(0,0);
}


   //该方法在activity start时候调用
@Override
public void onActivityStopped(Activity activity) {
   当前activityCount减1
    mActivityCount--;

}
}

pinUtil介绍(pinActivity中大量使用,先介绍)

 public class PinUtil {
//添加动作
public static final String PIN_ADD = "pinAdd";
//确认动作
public static final String PIN_CONFIRM= "pinConfirm";
//删除动作
public static final String PIN_DELETE = "pinDelete";
//验证动作
public static final String PIN_VERIFY = "pinVerify";
//pin sharedpreferences name
public static final String PIN_SP="pinSp";
// pin sharedpreferences key  获取pin码数字字符串
public static final String PIN_TEXT="pinText";
// pin sharedpreferences key  获取pin码验证错误次数
public static final String PIN_COUNT="pinCount";
// pin sharedpreferences key  获取pin码锁定时间
public static final String PIN_TIME="pinTime";
// pin sharedpreferences key  获取pin码样式
public static final String PIN_STYLE="pinStyle";

public static final String PIN_BORDER_STYLE = "pinBorderStyle";
public static final String PIN_CIRCLE_STYLE = "pinCircleStyle";

private PinUtil() {
}




public static void setPin(String pin) {
    SharedPreferences preferences = BaseApp.getInstance().getSharedPreferences(PIN_SP, BaseApp.getInstance().MODE_PRIVATE);
    SharedPreferences.Editor edit = preferences.edit();
    edit.putString(PIN_TEXT, pin);
    edit.commit();
}

public static String getPin() {
    SharedPreferences preferences = BaseApp.getInstance().getSharedPreferences(PIN_SP, BaseApp.getInstance().MODE_PRIVATE);
    return preferences.getString(PIN_TEXT, null);
}

public static void setTime(long time) {
    SharedPreferences preferences = BaseApp.getInstance().getSharedPreferences(PIN_SP, BaseApp.getInstance().MODE_PRIVATE);
    SharedPreferences.Editor edit = preferences.edit();
    edit.putLong(PIN_TIME, time);
    edit.commit();
}

public static long getTime() {
    SharedPreferences preferences = BaseApp.getInstance().getSharedPreferences(PIN_SP, BaseApp.getInstance().MODE_PRIVATE);
    return preferences.getLong(PIN_TIME, 0);
}

public static void setCount(int time) {
    SharedPreferences preferences = BaseApp.getInstance().getSharedPreferences(PIN_SP, BaseApp.getInstance().MODE_PRIVATE);
    SharedPreferences.Editor edit = preferences.edit();
    edit.putInt(PIN_COUNT, time);
    edit.commit();
}

public static int getCount() {
    SharedPreferences preferences = BaseApp.getInstance().getSharedPreferences(PIN_SP, BaseApp.getInstance().MODE_PRIVATE);
    return preferences.getInt(PIN_COUNT, 0);
}
public static void setPinStyle(String pin) {
    SharedPreferences preferences = BaseApp.getInstance().getSharedPreferences(PIN_SP, BaseApp.getInstance().MODE_PRIVATE);
    SharedPreferences.Editor edit = preferences.edit();
    edit.putString(PIN_STYLE, pin);
    edit.commit();
}

public static String getPinStyle() {
    SharedPreferences preferences = BaseApp.getInstance().getSharedPreferences(PIN_SP, BaseApp.getInstance().MODE_PRIVATE);
    return preferences.getString(PIN_STYLE, null);
}
}

pinActiviy介绍

  public class PinActivity extends BaseActivity implements CircleEditText.OnCircleEditTextListener, NumberInputView.OnNumberInputViewListener {

private TextView mTv;
private CircleEditText mCircleEditText;
private NumberInputView mNumberInputView;
private String mPinAction;
private String mPinCode;
private boolean mStartMain;
//失败次数,每失败一次 次数+1,到达指定次数进入锁定状态
//进入锁定状态后开始倒计时,倒计时完毕后重置状态
//注意,次数和倒计时时间都是本地持久化数据,不会因为程序杀掉而丢失(逻辑量大了一丢丢...)
private int mCount;
private CountDownTimer mDownTimer;
private Button mMneBN;
private TextView mCountTv;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_pin);
    invadeStatusBar2();
    mTv = (TextView) findViewById(R.id.tv);
    mCountTv = (TextView) findViewById(R.id.count_tv);
    mCircleEditText = (CircleEditText) findViewById(R.id.edit);
    mNumberInputView = (NumberInputView) findViewById(R.id.input);
    mCircleEditText.setListener(this);
    mNumberInputView.setListener(this);
    mMneBN = (Button) findViewById(R.id.mne_bn);
    intentWork();
    mneWork();
}

 //初始化工作,根据类型初始化提示字符串
private void intentWork() {
    mPinAction = getIntent().getStringExtra(ConstantUtil.Intent.PIN_ACTION);

    switch (mPinAction) {
        case PinUtil.PIN_ADD:
            mTv.setText("请设置pin码");
            break;
        case PinUtil.PIN_CONFIRM:
            //第一次设置pin码  本次进行重复确认
            mPinCode = getIntent().getStringExtra(ConstantUtil.Intent.PIN_CODE);
            mTv.setText("请确认pin码");
            break;
        case PinUtil.PIN_DELETE:
            mTv.setText("删除pin码");
            break;
        case PinUtil.PIN_VERIFY:
            mStartMain = getIntent().getBooleanExtra(ConstantUtil.Intent.PIN_START_MAIN, false);
            mTv.setText("验证pin码");
            break;
    }
}

//输入框输入完毕回调(根据之前博客了解自定义输入框)
@Override
public void OnCircleEditTextComplete(CircleEditText editText, String text) {
    switch (mPinAction) {
        case PinUtil.PIN_ADD:
            Intent intent = new Intent(this, PinActivity.class);
            intent.putExtra(ConstantUtil.Intent.PIN_ACTION, PinUtil.PIN_CONFIRM);
            intent.putExtra(ConstantUtil.Intent.PIN_CODE, text);
            startActivity(intent);
            finish();
            break;
        case PinUtil.PIN_CONFIRM:
            if (!text.equals(mPinCode)) {
                Toast.makeText(this, " 两次输入pin码不一致", Toast.LENGTH_SHORT).show();
                errorAnim();
                return;
            }
            PinUtil.setPin(text);
            Toast.makeText(this, " pin码设置成功 ", Toast.LENGTH_SHORT).show();
            finish();
            break;
        case PinUtil.PIN_DELETE:
            if (!text.equals(PinUtil.getPin())) {
                Toast.makeText(this, " pin码不正确 ", Toast.LENGTH_SHORT).show();
                errorAnim();
                return;
            }
            Toast.makeText(this, " pin码清除成功 ", Toast.LENGTH_SHORT).show();
            PinUtil.setPin(null);
            PinUtil.setPinStyle(null);
            finish();
            break;
        case PinUtil.PIN_VERIFY:
            if (!text.equals(PinUtil.getPin())) {
                errorAnim();
                countWork();
                return;
            }
            //认证成功
            resetStatus();
            if (mStartMain) {
                //第一次启动的话,需要验证之后进入首页面.不是第一次启动,直接关闭pin页面即可
                myStartActivity(MainActivity.class);
            }
            finish();
            overridePendingTransition(0, 0);

            break;
    }
}
//错误次数相关处理
private void countWork() {
    if (mCount == 4) {
        PinUtil.setTime(System.currentTimeMillis());
        //超过一定次数,进入锁定状态并倒计时解锁。
        downTimerWork(600000);
        mNumberInputView.setVisibility(View.GONE);
        mMneBN.setVisibility(View.VISIBLE);
        return;
    }
    mCountTv.setVisibility(View.VISIBLE);
    mCountTv.setText("密码错误,还剩" + (4 - mCount) + "次输入机会");
    mCount++;
}
//锁定状态倒计时相关工作
private void downTimerWork(long difference) {
    mDownTimer = new CountDownTimer(difference, 1000) {
        @Override
        public void onTick(long millisUntilFinished) {
            mTv.setText("已进入锁定状态,请" + (int) (millisUntilFinished / 1000) + "秒后重试");
        }

        @Override
        public void onFinish() {
            resetStatus();
        }

    };
    mDownTimer.start();
}

//倒计时结束恢复可输入状态
private void resetStatus() {
    mCount = 0;
    PinUtil.setTime(0);
    PinUtil.setCount(mCount);
    mNumberInputView.setVisibility(View.VISIBLE);
    mTv.setText("请输入当前钱包密码");
    mCountTv.setVisibility(View.GONE);
    mMneBN.setVisibility(View.GONE);
}

//自定义输入键盘(可根据之前博客了解自定义键盘)
@Override
public void onNumberClick(NumberInputView view, int num) {
    //mBorderEditText 字符串长度+1
    if (!mCircleEditText.isEnabled()) {
        return;
    }
    String s = mCircleEditText.getText().toString();
    s += num;
    mCircleEditText.setText(s);
}

@Override
public void onClearClick(NumberInputView view) {
    mCircleEditText.clear();
}

@Override
public void onBackwardClick(NumberInputView view) {
    //mBorderEditText 字符串长度-1
    String s = mCircleEditText.getText().toString();
    if (s.length() == 0) {
        return;
    }
    String substring = s.substring(0, s.length() - 1);
    mCircleEditText.setText(substring);
}
 //输入错误动画
private void errorAnim() {
    Animation animation = AnimationUtils.loadAnimation(
            this, R.anim.shake);
    mCircleEditText.startAnimation(animation);
    animation.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
            mCircleEditText.setEnabled(false);
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            mCircleEditText.clear();
            mCircleEditText.setEnabled(true);
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    });
}

@Override
protected void onResume() {
    super.onResume();
   //锁定时间相关工作
    timerWork();

}
 //锁定时间相关工作
private void timerWork() {
    if (!PinUtil.PIN_VERIFY.equals(mPinAction)) {
        return;
    }
    mCount = PinUtil.getCount();
    long time = PinUtil.getTime();
    if (time == 0) {
        mNumberInputView.setVisibility(View.VISIBLE);
        return;
    }
    long currentTimeMillis = System.currentTimeMillis();
    long difference = currentTimeMillis - time;
    if (difference < 600000) {
        mNumberInputView.setVisibility(View.GONE);
        mMneBN.setVisibility(View.VISIBLE);
        mCountTv.setVisibility(View.VISIBLE);
        downTimerWork(600000 - difference);
        return;
    } else {
        resetStatus();
    }

}

@Override
protected void onPause() {
    super.onPause();
    //保存状态并取消倒计时工作
    PinUtil.setCount(mCount);
    if (mDownTimer != null) {
        mDownTimer.cancel();
        mDownTimer = null;
    }
}

/**
 * 如果是验证pin码状态则设置不能返回.
 */
@Override
public void onBackPressed() {
    switch (mPinAction) {
        case PinUtil.PIN_ADD:
        case PinUtil.PIN_CONFIRM:
        case PinUtil.PIN_DELETE:
            super.onBackPressed();
            break;
        case PinUtil.PIN_VERIFY:
            break;
    }
}
 }

以上是所有相关逻辑,非重点代码不再啰嗦。

总结

开发中免不了用到各种锁屏,无论是pin码也好,其他也罢,希望上面的意图逻辑能帮助到大家,谢谢~

地址:https://github.com/HoldMyOwn/PinLock

相关文章

  • 好用解耦的Pin码应用锁(附代码库)

    起因 各位小伙伴,开发中肯定会有应用锁的需求。手势锁也好、pin码锁也罢,亦或是指纹锁。不管哪一类,它们的基本逻辑...

  • Jetpact 之 LifeCycle

    LifeCycle的诞生: 为了解决代码解耦的问题,将系统组件和普通组件尽可能的解耦 LifeCycle应用: 使...

  • iOS组件化 (pod私有库,包含静态库)

    因项目业务的不断发展,应用的代码体积越来越大,代码耦合也较为严重,所以考虑先对各模块进行解耦,然后通过pod私有库...

  • 解耦

    解耦 对于大型重构, 最有效的手段就是 解耦, 解耦的目的使实现代码高聚合、松耦合。 解耦为何如此...

  • 2022-05-01代码重构 -- 大小规模重构

    大规模高层次重构 解耦代码 “解耦”为何如此重要? 过于复杂的代码往往在可读性、可维护性上都不友好。解耦保证代码松...

  • 设计模式

    设计模式要干的事情就是解耦。创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的...

  • 为什么工厂模式可以解耦?why?

    1、聊聊解耦? 耦合:代码之间的关联关系称为耦合,具有强关联关系的称为强耦合。 解耦:解除代码之间的关联关系,使每...

  • [Android] MVVM设计模式及实例

    MVVM简介 可能你使用过 MVP 设计模式来对代码进行解耦, 但是当前谷歌发布 Data Binding 库来...

  • 桥接模式

    一、桥接模式 解耦图解 示例代码

  • AMD, CMD, COMMONJS

    为什么要使用模块化? Web应用的复杂度提高 避免命名污染 管理依赖 代码解耦, 提高代码的复用性 CMD、AMD...

网友评论

本文标题:好用解耦的Pin码应用锁(附代码库)

本文链接:https://www.haomeiwen.com/subject/ejsadftx.html