验证码按钮在项目的使用频率应该是100%,现在大多数应用都会采用手机号码登陆,通常都会支持发送验证码登陆的功能。
我在项目中也遇到这样的功能,特别在忘记密码的界面,我注意到一点:当发送验证码之后,要60秒之后才能再发,如果退出界面,重新进入同样需要等待时间结束。
其中分析一下流程应该是这样的:
- 倒计时按钮点击:发送验证码
- 发送验证码成功:禁用按钮 & 倒计时开始
- 倒计时结束之后:恢复按钮
针对这种场景进行分析,如果采用继承Button的方式对项目耦合性太强了,以后不需要移除也特别麻烦。所以抽象一个工具类进行对Button自动化设置会更好,以后不需要了可以随时移除然后重新实现逻辑。
既然是倒计时功能,所以项目需要使用到相关的技术,在安卓开发中,我总结了如下几种倒计时方法:
- Handler实现倒计时(sendEmptyMessageDelayed方法)
- Thread实现倒计时
- View.postDelayed()实现倒计时
- CountDownTimer实现倒计时
CountDownTimer是安卓专门未倒计时设计的一个类,使用起来也非常方便,所以我也选择它作为实现的基础。他是一个抽象类,使用时需要继承并实现onTick()和onFinish()方法。
/** * 构造函数(millisInFuture:倒计时总时间,countDownInterval:触发回调的时间间隔) */public CountDownTimer(long millisInFuture, long countDownInterval)/** * 每次倒计时都会触发回调,millisUntilFinished表示剩余的时间。 */public abstract void onTick(long millisUntilFinished);/** * 倒计时结束后回调 */public abstract void onFinish();
再想想自己设计这个工具类的具体思想:
- autoHandleWhenActivityCreate(Button indicator) :在onCreate()中调用,绑定按钮并初始化。
- autoHandleWhenActivityDestroy() :在onDestroy()中调用,因为CountDownTimer本质上是Handler实现的,所以处理不当会造成内存泄漏。
- autoHandleRequestStartTimer() :调用此方法会立即开始倒计时,按钮的倒计时文字将会变化。
贴一贴工具类的具体使用方法:
public class ForgetActivity extends AppCompatActivity { Button uaSendCode; // 第一步:创建静态计时器类(当前界面有效,重复进入倒计时依然继续) private static CountDownTimer mCountDown = new CountDownTimer(60, "发送验证码(%s)", "发送验证码", R.drawable.register_bg_send, R.drawable.register_bg_unsend); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.forget_activity_main); uaSendCode = findViewById(R.id.uaSendCode); // 第二步:绑定按钮 mCountDown.autoHandleWhenActivityCreate(uaSendCode); uaSendCode.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 注意:启动计时器需要调用下面的方法 mCountDown.autoHandleRequestStartTimer(); } }); } @Override protected void onDestroy() { super.onDestroy(); //第三步:释放内部引用,防止内存泄漏 mCountDown.autoHandleWhenActivityDestroy(); }}
下面贴上具体实现的逻辑:
package com.anbang.family.library;import android.view.View;import android.widget.Button;/** * 按钮倒计时 */public class CountDownTimer extends android.os.CountDownTimer { private Button mIndicator; private String mCountDownFormatString = "发送验证码(%s)"; private String mCountDownFinishString = "发送验证码"; private int mEnableDrawable; private int mUnableDrawable; private int mSecond; private int mCount; /** * 是否启动过计时器(mCount一开始不为0,所以配合这个标志做判断是否start计时器) */ private boolean mAlreadyStart = false; public CountDownTimer(int second, String countDownFormatString, String countDownFinishString, int enableDrawable, int unableDrawable) { super((second <= 0 ? 60 : second) * 1000, 1000); this.mSecond = second; this.mCount = second; this.mCountDownFormatString = countDownFormatString; this.mCountDownFinishString = countDownFinishString; this.mEnableDrawable = enableDrawable; this.mUnableDrawable = unableDrawable; } public int getmCount() { return mCount; } public void autoHandleWhenActivityCreate(Button indicator) { this.mIndicator = indicator; if (mCount <= 0 || !mAlreadyStart) { mIndicator.setEnabled(true); mIndicator.setText(mCountDownFinishString); mIndicator.setBackgroundResource(mEnableDrawable); } else { mIndicator.setEnabled(false); mIndicator.setBackgroundResource(mUnableDrawable); mIndicator.setText(String.format(mCountDownFormatString, mCount)); } } public void autoHandleWhenActivityDestroy() { mIndicator = null; } public void autoHandleRequestStartTimer() { if (mCount <= 0 || !mAlreadyStart) { mAlreadyStart = true; mCount = mSecond <= 0 ? 60 : mSecond; if (mIndicator != null) { unableIndicator(mCount); } start(); } } @Override public void onTick(long millisUntilFinished) { mCount--; unableIndicator((int) (millisUntilFinished / 1000)); } @Override public void onFinish() { enableIndicator(); } private void unableIndicator(int second) { if (mIndicator != null) { mIndicator.setEnabled(false); mIndicator.setBackgroundResource(mUnableDrawable); mIndicator.setText(String.format(mCountDownFormatString, second)); } } private void enableIndicator() { if (mIndicator != null) { mIndicator.setEnabled(true); mIndicator.setText(mCountDownFinishString); mIndicator.setBackgroundResource(mEnableDrawable); } }}