等待總是讓人感到焦急和厭煩的,特別是看不到進展的等待。所以為了不讓使用者痴痴地等,我們在進行某些耗時操作時,一般都要設計一個進度條或者倒數計時器,讓進度視覺化,告訴使用者“等待之後更精彩”。在使用簡訊驗證碼註冊或者登入App就可以看到這樣的設計:點選“傳送驗證碼”的按鈕之後,按鈕上就會出現倒數計時(一般為60秒),倒數計時結束之後,按鈕的文字就會變成“重新傳送”。
在Android中要實現這樣的效果可以使用Handler傳送訊息,但其實還有一個已經封裝好的抽象類可以幫上忙,那就是CountDownTimer
,利用它,我們可以很輕鬆地實現倒數計時。很久以前我就用過這個類,但是這幾天寫時發現了一個當初沒有注意到的坑,因此打算寫一篇部落格記錄下來。
1、需求分析
- 點選按鈕之後,按鈕文字變為“ns後傳送驗證碼”(n為倒數計時讀數);
- 為了讓倒數計時更加醒目,將秒數和單位設為藍色;
- 倒數計時結束之後,按鈕的文字顯示為“重新傳送”。
瞄一眼效果圖:
2、工程建立和佈局編寫
建立工程就不用多說了,由於我們只需要看到按鈕上的倒數計時效果,不必輸入手機號碼,所以只要在介面上簡單地放置一個按鈕即可:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context="com.lindroid.countdowndemo.MainActivity">
<Button
android:id="@+id/btn_captcha"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#c7c7c7"
android:text="獲取驗證碼"
android:textAllCaps="false"
android:textColor="@android:color/black"
android:textSize="18sp" />
</RelativeLayout>複製程式碼
3、如何使用CountDownTimer
CountDownTimer
倒數計時器的使用並不難,我們可以建立一個類繼承它,並實現它的建構函式和重寫兩個方法:
private CountTimer countTimer;
/**
* 點選按鈕後倒數計時
*/
class CountTimer extends CountDownTimer {
public CountTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
/**
* 倒數計時過程中呼叫
*
* @param millisUntilFinished
*/
@Override
public void onTick(long millisUntilFinished) {
}
/**
* 倒數計時完成後呼叫
*/
@Override
public void onFinish() {
}
}複製程式碼
大體的框架如上所述,我來稍微解釋一下。首先是建構函式,裡面有兩個引數:
millisInFuture
:倒數計時的總時間,單位為毫秒countDownInterval
:倒數計時的時間間隔,單位為毫秒
比如我想設定10秒的倒數計時,每隔1秒就讀一次數,那麼初始化就可以將數值傳入:
除了建構函式,還有兩個方法,它們的作用分別如下:CountTimer countTimer = = new CountTimer(10000, 1000);複製程式碼
onTick
:倒數計時過程中呼叫onFinish
:倒數計時結束後呼叫
那麼怎麼開啟倒數計時呢?只需要用countTimer去呼叫start方法就可以了。另外,為了節省資源,在Activity銷燬時應該停止倒數計時:
@Override
protected void onDestroy() {
super.onDestroy();
countTimer.cancel();
}複製程式碼
到這裡,你應該知道怎麼使用如何使用CountDownTimer了吧?如果還有疑問,可以在文末下載完整的程式碼。
4、實現簡單的倒數計時效果
現在我們先來實現點選按鈕後就進行倒數計時讀數的效果,程式碼如下:
CountTimer countTimer = new CountTimer(10000, 1000);
/**
* 點選按鈕後倒數計時
*/
class CountTimer extends CountDownTimer {
public CountTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
/**
* 倒數計時過程中呼叫
*
* @param millisUntilFinished
*/
@Override
public void onTick(long millisUntilFinished) {
Log.e("Tag", "倒數計時=" + (millisUntilFinished/1000));
btnCaptcha.setText(millisUntilFinished / 1000 + "s後重新傳送");
//設定倒數計時中的按鈕外觀
btnCaptcha.setClickable(false);//倒數計時過程中將按鈕設定為不可點選
btnCaptcha.setBackgroundColor(Color.parseColor("#c7c7c7"));
btnCaptcha.setTextColor(ContextCompat.getColor(context, android.R.color.black));
btnCaptcha.setTextSize(16);
}
/**
* 倒數計時完成後呼叫
*/
@Override
public void onFinish() {
Log.e("Tag", "倒數計時完成");
//設定倒數計時結束之後的按鈕樣式
btnCaptcha.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_blue_light));
btnCaptcha.setTextColor(ContextCompat.getColor(context, android.R.color.white));
btnCaptcha.setTextSize(18);
btnCaptcha.setText("重新傳送");
btnCaptcha.setClickable(true);
}
}複製程式碼
倒數計時的讀數是實時的,毫無疑問應該在onTick
方法中處理這些邏輯,倒數計時完成後要將按鈕文字改為“重新傳送”,這個可以交給onFinish
。
執行一下,點選按鈕,倒數計時成功出現了,但是再點幾次,詭異的事情發生了:有時候倒數計時讀數會漏掉某個數字,比如從10直接就到8了,列印出來的日誌是這樣的:
這……到底是怎麼回事?少掉的一秒難道是被某人給續了麼?
5、CountDownTimer誤差解決
為了找回生命中的這一秒鐘,我在一個技術群裡和小夥伴們討論了很久,最後算是逃過了時間黑洞的魔爪。
我們採用的倒數計時讀數是將millisUntilFinished
除於1000得到的,這裡就有一個小小的陷阱了:millisUntilFinished
是長整型變數,除於1000之後得到是整數部分。我們可以將millisUntilFinished
的值列印出來看看:
現在明白為什麼看不到讀數9了嗎?那是因為程式執行雖然很快,但再快也是需要時間的,所以從10秒倒數計時到9秒時,millisUntilFinished會比9000稍小一點,是8999,而長整型8999除於1000之後就得到8了。當然,既然是誤差那就有多種情況,少掉的數字不一定是9,這裡只是我針對我遇到的情況而言。
知道原因之後就好辦了,我們可以先將millisUntilFinished
轉換成double型別後再除於1000,這樣就可以保留小數部分了,然後使用Math類中的round
方法四捨五入,但是這樣倒數計時的話會從10到2,這顯然不行,所以再減去1,讓它從9到1。修改後的onTick
方法程式碼是這樣的:
public void onTick(long millisUntilFinished) {
//處理後的倒數計時數值
int time = (int) (Math.round((double) millisUntilFinished / 1000) - 1);
btnCaptcha.setText(String.valueOf(time)+"s後重新傳送");
//設定倒數計時中的按鈕外觀
btnCaptcha.setClickable(false);//倒數計時過程中將按鈕設定為不可點選
btnCaptcha.setBackgroundColor(Color.parseColor("#c7c7c7"));
btnCaptcha.setTextColor(ContextCompat.getColor(context, android.R.color.black));
btnCaptcha.setTextSize(16);
}複製程式碼
執行後試試,就可以發現失去的那一秒又回來啦。
6、給倒數計時讀數和單位設定前景色
給同一字串中的不同字元設定不同的字型顏色,這就需要用到SpannableString與SpannableStringBuilder相關的知識了,限於篇幅,這裡就不贅述了,可以參考這篇文章:SpannableString與SpannableStringBuilder。這裡只簡單介紹一下:
6.1 拼接字串
int time = (int) (Math.round((double) millisUntilFinished / 1000) - 1);
//拼接要顯示的字串
SpannableStringBuilder sb = new SpannableStringBuilder();
sb.append(String.valueOf(time));
sb.append("s後重新傳送");複製程式碼
6.2 設定要顯示的文字樣式
//字元“後”在字串中的下標
int index = String.valueOf(sb).indexOf("後");
//給秒數和單位設定藍色前景色
ForegroundColorSpan colorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, android.R.color.holo_blue_dark));
sb.setSpan(colorSpan, 0, index, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
btnCaptcha.setText(sb);複製程式碼
這次執行之後就可以看到跟效果圖一樣的效果了。最後給一下原始碼:
CountDownTimerDemo