日常开发中在验证码发送等功能中会有倒计时需求,那么就通过继承TextView实现一个倒计时控件CountdownTextView,倒计时通过Timer实现,状态可自动切换,首先看下实现效果:
接下来通过代码解读一下
public class CountdownTextView extends AppCompatTextView {
private static final int THOUSAND = 1000;
private Handler mHandler = new Handler();
private String countdownText;
private String normalText;
private int countdownTime;//s
private int intervalTime = 1;//s
private Timer mCountdownTimer;
private AttributeSet mAttributeSet;
public CountdownTextView(Context context) {
this(context, null);
}
public CountdownTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CountdownTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mAttributeSet = attrs;
initView(context, attrs);
}
private void initView(Context context, @Nullable AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CountdownTextView);
countdownTime = typedArray.getInteger(R.styleable.CountdownTextView_countdownTime, 60);
intervalTime = typedArray.getInteger(R.styleable.CountdownTextView_intervalTime, 1);
normalText = typedArray.getString(R.styleable.CountdownTextView_normalText);
countdownText = typedArray.getString(R.styleable.CountdownTextView_countdownText);
if (TextUtils.isEmpty(normalText)) {
normalText = getContext().getString(R.string.get_verification_code);
}
if (TextUtils.isEmpty(countdownText)) {
countdownText = "";
}
setText(normalText);
setClickable(true);
typedArray.recycle();
}
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
}
/**
* 点击自动开始计时
**/
@Override
public boolean performClick() {
startCountdown();
return super.performClick();
}
public void startCountdown() {
cancelCountdown();
setEnabled(false);
mCountdownTimer = new Timer();
mCountdownTimer.schedule(new TimerTask() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
countdownTime -= intervalTime;
if (countdownTime == 0) {
setText(normalText);
cancelCountdown();
} else {
setText(countdownText + countdownTime + "s");
}
}
});
}
}, 0, intervalTime * THOUSAND);
}
public void cancelCountdown() {
if (mCountdownTimer != null) {
mCountdownTimer.cancel();//注意:这儿必须先停止倒计时再初始化控件,否则有可能时间会乱
initView(getContext(), mAttributeSet);
setEnabled(true);
}
}
/**
* 与Window解绑自动停止计时
**/
@Override
protected void onDetachedFromWindow() {
cancelCountdown();
super.onDetachedFromWindow();
}
}
1.自定义属性
主要定义了倒计时显示文本内容、倒计时时长、倒计时间隔等,通过xml设置。
2.设置自动开启和结束机制
设置点击控件后自动开始计时,与Window解绑后自动停止计时。
开始方法是在performClick()中调用的,为啥不在点击事件中调用呢?因为点击事件属于事件传递最终环节,而且每个控件点击事件只能设置一次,所以若在点击事件中设置的话只能手动调用开始计时方法了,而点击事件会先传递到performClick(),然后再到onClick()方法,所以我们只要在performClick()中开启倒计时就可以。需要说明一点,因为控件是继承自TextView,TextView默认clickable为false,需要手动设置setClickable(true),不然事件就不会传递到performClick()了。
结束方法是在onDetachedFormWindow()中调用,控件与Window解绑时该方法被回调,从而自动停止倒计时。
还有一点就是在开始计时后该控件不能再次被点击直到倒计时结束恢复初始状态,通过setEnabled(false/true)来实现。至于为什么clickable为false就不能传递到点击事件,enabled为false就不能响应点击,具体请查看View的dispatchTouchEvent()方法。
3.控件使用
使用和TextView一样样的,就是setEnabled()和setClickable()别乱使用,否则就破坏整个机制了,开始计时方法startCountdown()和技术计时方法cancelCountdown()对外暴露,可随时停止结束计时。
4.拓展
至于其他可在此机制上自由拓展,如控件样式、字体大小等。
到这,一个简单的倒计时控件就定义完了,读友有疑问或不同见解可留言交流!