起因
各位小伙伴,开发中肯定会有应用锁的需求。手势锁也好、pin码锁也罢,亦或是指纹锁。不管哪一类,它们的基本逻辑都是差不多的,我这边以pin码锁为基础介绍实现方案,大家可根据需要拓展。
介绍
一:关于pin码锁这块,我做了两种基本样式,大家可以在此基础上修改。
二:里面有添加pin码、确认pin码、验证pin码、删除pin码四种动作。
三:同时,这边不仅实现了锁屏的需求,而且拓展了输入错误的次数统计处理。如果,错误超过3次(大家可修改次数),则会进入倒计时锁定状态(锁定时长可修改),期间无法进行输入验证。
四:错误次数、锁定状态,我这边做了状态保留,不管你是进入后台亦或杀掉应用重新启动。我这边都会恢复上次的状态。
效果
两种样式


流程(添加、确认、验证、删除)

锁定状态(超过一定错误次数)

上面图片大致介绍了,该项目包含的东西。样式、流程动作、以及锁定状态。样式方面可根据需要调整,完全无耦合。流程动作,用的全是同一个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码也好,其他也罢,希望上面的意图逻辑能帮助到大家,谢谢~
网友评论