在日常开发中,获取验证码是一个常见的功能,通常验证码倒计时的实现思路都是使用CountDownTimer来实现,但是存在一个问题就是当页面关闭之后重新进入页面,倒计时是不会继续进行的,如果后端验证码接口做了时间限制,那么我们再次请求的时候就会报错,用户体验不好。
实现思路
使用CountDownTimer进行倒计时,在开始倒计时的时候,把 当前时间+倒计时总时间 持久化存储,再次打开页面的时候判断一下当前时间是否在倒计时时间的范围内,如果在就继续倒计时。
效果如下
倒计时工具类
持久化存储我使用了腾讯的MMKV,当前你也可以使用SharedPreference 如果你使用的也是MMKV,别忘了在Application中初始化
package com.lzk.jetpacktest.code
import android.os.CountDownTimer
import android.util.Log
import com.tencent.mmkv.MMKV
/**
* @Author: LiaoZhongKai
* @Date: 2021/8/17 16:38
* @Description: 验证码倒计时工具类
*/
object CodeCountDownUtil {
private const val KEY_TIME = "TotalTimeMills"
private val mMkv: MMKV = MMKV.mmkvWithID("CodeCountDownUtil")
private var mTotalTimeMills = 0L
private var mListener: OnCountDownListener? = null
private lateinit var mCountDownTimer: CountDownTimer
/**
* 开始倒计时
* [totalTimeMills] 倒计时时间毫秒值
*/
fun start(totalTimeMills: Long,listener: OnCountDownListener){
mListener = listener
mTotalTimeMills = totalTimeMills+System.currentTimeMillis()
mMkv.encode(KEY_TIME, mTotalTimeMills)
mCountDownTimer = object : CountDownTimer(totalTimeMills,1000) {
override fun onTick(millisUntilFinished: Long) {
mListener?.onTick(millisUntilFinished / 1000)
}
override fun onFinish() {
mListener?.onFinish()
reset()
}
}
mCountDownTimer.start()
}
/**
* 取消倒计时
*/
fun cancel(){
if (::mCountDownTimer.isInitialized){
mCountDownTimer.cancel()
}
mListener = null
}
/**
* 重置倒计时,将剩余时间置为0
* 例如某些需求下,上一次获取验证码的倒计时时间未结束,此时将获取验证码页面关闭之后再次打开页面,可
* 以直接获取验证码,此时可以在onDestroy()中调用reset()而不是cancel()。
*/
fun reset(){
mMkv.encode(KEY_TIME,0L)
cancel()
mTotalTimeMills = 0L
}
/**
* 获取当前的倒计时时间毫秒值
*/
fun getCountTimeMills(): Long{
mTotalTimeMills = mMkv.decodeLong(KEY_TIME)
return mTotalTimeMills - System.currentTimeMillis()
}
/**
* 当前时间是否在倒计时范围内
* @return true: 倒计时正在进行 false:倒计时未进行
*/
fun isCounting(): Boolean{
mTotalTimeMills = mMkv.decodeLong(KEY_TIME)
return System.currentTimeMillis() < mTotalTimeMills
}
interface OnCountDownListener{
/**
* 倒计时
* [seconds] 倒计时剩余时间,秒为单位
*/
fun onTick(seconds: Long)
/**
* 倒计时结束
*/
fun onFinish()
}
}
使用示例
package com.lzk.jetpacktest.code
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import com.lzk.jetpacktest.R
import com.lzk.jetpacktest.databinding.ActivityVerificationCodeBinding
/**
* @Author: LiaoZhongKai
* @Date: 2021/8/18 9:22
* @Description: 验证码页面
*/
class VerificationCodeActivity : AppCompatActivity() {
companion object{
private const val COUNT_DOWN_TIME = 60*1000L
}
private lateinit var mBinding: ActivityVerificationCodeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this,R.layout.activity_verification_code)
initView()
initEvent()
}
//千万不要忘记在onDestroy中取消倒计时
//千万不要忘记在onDestroy中取消倒计时
//千万不要忘记在onDestroy中取消倒计时
override fun onDestroy() {
super.onDestroy()
CodeCountDownUtil.cancel()
}
private fun initView(){
//如果当前时间仍在倒计时范围内,则显示倒计时
if (CodeCountDownUtil.isCounting()){
CodeCountDownUtil.start(CodeCountDownUtil.getCountTimeMills(),mCountDownListener)
mBinding.btn.isEnabled = false
}else{//否则显示获取验证码
mBinding.btn.text = "获取验证码"
}
}
private val mCountDownListener = object : CodeCountDownUtil.OnCountDownListener{
override fun onTick(seconds: Long) {
Log.d("TAG","onTick:${seconds}")
mBinding.btn.text = "$seconds 秒后重新获取"
}
override fun onFinish() {
mBinding.btn.isEnabled = true
mBinding.btn.text = "获取验证码"
}
}
private fun initEvent(){
//获取验证码按钮
mBinding.btn.setOnClickListener {
//模拟验证码发送成功
Toast.makeText(this,"验证码已发送",Toast.LENGTH_SHORT).show()
//按照正常流程,以下代码应该在验证码发送成功之后再调用
//开始倒计时
CodeCountDownUtil.start(COUNT_DOWN_TIME,mCountDownListener)
mBinding.btn.isEnabled = false
}
//重置验证码
//模拟登录成功后要重置
mBinding.btnReset.setOnClickListener {
CodeCountDownUtil.reset()
mBinding.btn.isEnabled = true
mBinding.btn.text = "获取验证码"
}
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".code.VerificationCodeActivity">
<Button
android:id="@+id/btn"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:text="获取验证码" />
<Button
android:id="@+id/btn_reset"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:text="重置验证码" />
</LinearLayout>
</layout>