验证码按钮在项目的使用频率应该是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);
}
}
}