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

code小生,一个专注 Android 领域的技术分享平台

作者:Allen___
链接:https://www.jianshu.com/p/4da743168e42
声明:本文是 Allen___ 原创投稿,转载等请联系作者获得授权。

起因

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

介绍

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

效果

两种样式

circleStyle.pngborderStyle.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

键盘相关

仿微信表情输入键盘(支持 Gif 表情图文混排 )

Android 键盘适配-中英文适配

教你打造好用KeyBoard(附代码库)

猜你喜欢

转载自blog.csdn.net/h176nhx7/article/details/80504452
今日推荐