Android之實現(xiàn)倒計時的那點事兒

前言

LZ-Says:今天收到轉(zhuǎn)正通知了,內(nèi)心也就12s開心柔滔,隨之而來的就是不平不淡溢陪。16年10月18號入職,感覺一切都好像眨眼之間睛廊,過得好快形真,各種忙
答應(yīng)了自己,要好好努力超全。答應(yīng)了家人咆霜,要好好奮斗。答應(yīng)了心嘶朱,要好好堅持蛾坯。有什么理由不去努力

開始正題

好吧,今天一起回顧下疏遏,關(guān)于Android中實現(xiàn)倒計時功能吧~

今天為大家介紹倆種方式脉课,都可以實現(xiàn)倒計時功能。這倆種方式分別是:

  1. Handler+Thread
  2. CountDownTimer

想必大家對于第一種實現(xiàn)方式肯定不會陌生了财异,簡直So easy那再次回顧下第一種寫法

1.通過使用Handler+Thread實現(xiàn)倒計時

首先編寫布局文件

布局文件很簡單倘零,就是一個TextView,默認(rèn)顯示Handler獲取驗證碼戳寸,點擊TextView呈驶,進(jìn)行倒計時操作,完成后恢復(fù)默認(rèn)顯示疫鹊。

<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" >

    <TextView
        android:id="@+id/tv_show_h"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="193dp"
        android:text="Handler獲取驗證碼" />

</RelativeLayout>
放大招袖瞻,編寫Activity,實現(xiàn)效果~
public class MainActivity extends Activity {

    private TextView tvShowH;

    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            tvShowH.setText(msg.what - 1 + "s");
            if (msg.what == 0) {
                // 倒計時結(jié)束讓按鈕可用
                tvShowH.setEnabled(true);
                tvShowH.setText("Handler獲取驗證碼");
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvShowH = (TextView) findViewById(R.id.tv_show_h);

        tvShowH.setOnClickListener(listenerH);

    }

    private OnClickListener listenerH = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            tvShowH.setEnabled(false);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 10; i >= 0; i--) {
                        handler.sendEmptyMessage(i);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    };

}
來張效果圖瞅瞅唄~
這里寫圖片描述

2.通過CountDownTimer實現(xiàn)倒計時

CountDownTimer簡介

CountDownTimer是Android內(nèi)部封裝好的一個關(guān)于實現(xiàn)倒計時功能的類拆吆。所在包:package android.os;其內(nèi)部實現(xiàn)也是通過咱第一種實現(xiàn)方式聋迎,沒啥好說的,看看人家官方簡介吧

官方使用方式
 new CountdownTimer(30000, 1000) {

     public void onTick(long millisUntilFinished) {
         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
     }

     public void onFinish() {
         mTextField.setText("done!");
     }
  }.start();

The calls to onTick(long) are synchronized to this object so that one call to onTick(long) won't ever occur before the previous callback is complete. This is only relevant when the implementation of onTick(long) takes an amount of time to execute that is significant compared to the countdown interval.

從上面官方提供例子可以看出锈拨,如果想要使用CountDownTimer去實現(xiàn)倒計時砌庄,需要如下幾個步驟:

  1. 實例化CountDownTimer對象;
  2. 提供計時時間毫秒以及時間間隔毫秒;
  3. 重寫onTick()和onFinish()方法娄昆;
    那么這倆個方法分別都是什么作用呢佩微?
    3.1 onTick(long millisUntilFinished)
    參數(shù)millisUntilFinished是倒計時的剩余時間。在倒計時結(jié)束后會調(diào)用onFinish萌焰。
    3.2 onFinish()
    倒計時結(jié)束后需要執(zhí)行的操作可以寫在這里哺眯。
  4. start()開始倒計時~
開始Coding

首先在原有界面新增一個TextView,操作流程都一樣扒俯。

新增后layout
<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" >

    <TextView
        android:id="@+id/tv_show_c"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_show_h"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="25dp"
        android:text="CountDownTimer獲取驗證碼" />

    <TextView
        android:id="@+id/tv_show_h"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="178dp"
        android:text="Handler獲取驗證碼" />

</RelativeLayout>
新增后Activity
package com.example.hlqcountdowntimer;

import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView tvShowH, tvShowC;

    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            tvShowH.setText(msg.what - 1 + "s");
            if (msg.what == 0) {
                // 倒計時結(jié)束讓按鈕可用
                tvShowH.setEnabled(true);
                tvShowH.setText("Handler獲取驗證碼");
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvShowH = (TextView) findViewById(R.id.tv_show_h);

        tvShowH.setOnClickListener(listenerH);

        tvShowC = (TextView) findViewById(R.id.tv_show_c);

        tvShowC.setOnClickListener(listenerC);

    }

    private OnClickListener listenerH = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            tvShowH.setEnabled(false);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 10; i >= 0; i--) {
                        handler.sendEmptyMessage(i);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    };

    private OnClickListener listenerC = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            tvShowC.setEnabled(false);
            timer.start();
        }
    };

    private CountDownTimer timer = new CountDownTimer(10000, 1000) {

        @Override
        public void onTick(long millisUntilFinished) {
            long time = millisUntilFinished / 1000;
            if (time == 0) {
                tvShowC.setText(time + "秒后可重發(fā)");
                onFinish();
            }
            tvShowC.setText(time + "秒后可重發(fā)");
        }

        @Override
        public void onFinish() {
            tvShowC.setEnabled(true);
            tvShowC.setText("CountDownTimer獲取驗證碼");
        }
    };

}

來個效果圖~
這里寫圖片描述

不知道大家有沒有發(fā)現(xiàn)一個小問題奶卓,怎么到1秒時,會出現(xiàn)短暫延遲撼玄?而且使用CountDownTimer可以顯示0秒么夺姑?

關(guān)于以上問題,讓我們一起去看看人家是什么寫的掌猛,從他們寫的代碼中看看能不能發(fā)現(xiàn)相關(guān)蛛絲馬跡關(guān)于可以顯示0秒么這個問題盏浙,個人覺得,那必須啊就看怎么改他了~

深入了解CountDownTimer

讓我們一起進(jìn)入它內(nèi)部去瞅瞅~

//首先就是相關(guān)的介紹荔茬,LZ英文很LOW废膘,就不多說了~
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os;
//下面就是為大家簡單介紹如何使用CountDownTimer去實現(xiàn)倒計時效果
/**
 * Schedule a countdown until a time in the future, with
 * regular notifications on intervals along the way.
 *
 * Example of showing a 30 second countdown in a text field:
 *
 * <pre class="prettyprint">
 * new CountDownTimer(30000, 1000) {
 *
 *     public void onTick(long millisUntilFinished) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
 *     }
 *
 *     public void onFinish() {
 *         mTextField.setText("done!");
 *     }
 *  }.start();
 * </pre>
 *
 * The calls to {@link #onTick(long)} are synchronized to this object so that
 * one call to {@link #onTick(long)} won't ever occur before the previous
 * callback is complete.  This is only relevant when the implementation of
 * {@link #onTick(long)} takes an amount of time to execute that is significant
 * compared to the countdown interval.
 */
public abstract class CountDownTimer {

    /**
     * Millis since epoch when alarm should stop.
     */
    private final long mMillisInFuture;

    /**
     * The interval in millis that the user receives callbacks
     */
    private final long mCountdownInterval;

    private long mStopTimeInFuture;
    
    /**
    * boolean representing if the timer was cancelled
    */
    private boolean mCancelled = false;

    /**
     * @param millisInFuture The number of millis in the future from the call
     *   to {@link #start()} until the countdown is done and {@link #onFinish()}
     *   is called.
     * @param countDownInterval The interval along the way to receive
     *   {@link #onTick(long)} callbacks.
     */
    public CountDownTimer(long millisInFuture, long countDownInterval) {
        mMillisInFuture = millisInFuture;
        mCountdownInterval = countDownInterval;
    }

    /**
     * Cancel the countdown.
     */
    public synchronized final void cancel() {
        mCancelled = true;
        mHandler.removeMessages(MSG);
    }

    /**
     * Start the countdown.
     */
    public synchronized final CountDownTimer start() {
        mCancelled = false;
        if (mMillisInFuture <= 0) {
            onFinish();
            return this;
        }
        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
        mHandler.sendMessage(mHandler.obtainMessage(MSG));
        return this;
    }


    /**
     * Callback fired on regular interval.
     * @param millisUntilFinished The amount of time until finished.
     */
    public abstract void onTick(long millisUntilFinished);

    /**
     * Callback fired when the time is up.
     */
    public abstract void onFinish();


    private static final int MSG = 1;


    // handles counting down
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {

            synchronized (CountDownTimer.this) {
                if (mCancelled) {
                    return;
                }

                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

                if (millisLeft <= 0) {
                    onFinish();
                } else if (millisLeft < mCountdownInterval) {
                    // no tick, just delay until done
                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
                } else {
                    long lastTickStart = SystemClock.elapsedRealtime();
                    onTick(millisLeft);

                    // take into account user's onTick taking time to execute
                    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();

                    // special case: user's onTick took more than interval to
                    // complete, skip to next interval
                    while (delay < 0) delay += mCountdownInterval;

                    sendMessageDelayed(obtainMessage(MSG), delay);
                }
            }
        }
    };
}

CountDownTimer分析

首先從上面可以看出,一上來他就給我們進(jìn)行一些簡單的介紹慕蔚,之后就是提供使用方法丐黄,接下來就是重點,讓我們瞅瞅他們是什么寫的~

  1. 方法使用synchronized修飾孔飒,保證一次操作只能有一個進(jìn)行訪問灌闺;
  2. 在文章開頭,我簡單說過他內(nèi)部同樣是通過Handler去實現(xiàn)倒計時效果十偶,但是我們發(fā)現(xiàn)他使用了一個SystemClock.elapsedRealtime()菩鲜,那么這個東西又是什么呢园细?經(jīng)過百度后得知惦积,他的作用就是返回系統(tǒng)啟動到現(xiàn)在的毫秒數(shù),包含休眠時間猛频。不難理解狮崩,其實個人覺得和我們第一種寫法差不多。
  3. 那么問題他為什么會出現(xiàn)短暫卡頓呢鹿寻?其實大家在仔細(xì)查閱后會發(fā)現(xiàn)睦柴,當(dāng)它等于1時,接下來再走不就是0了么毡熏,小于等于0的時候同樣也會走一次坦敌,但是這次卻不會更新UI,所以造成一種假象,就會讓我們覺得界面出現(xiàn)了稍微卡頓狱窘。那么說到這杜顺,大家也就知道了怎么使用CountDownTimer去顯示0.下面請看修改后的CountDownTimer~

改造后的CountDownTimer

基于以上分析,我們明白蘸炸,只需要當(dāng)計時毫秒數(shù)小于等于0的時候躬络,他不會進(jìn)行更新UI操作,那么我們只需要讓它在小于等于0的時候搭儒,進(jìn)行更新UI操作即可穷当。

修改CountDownTimer

這部分很簡單,創(chuàng)建一個類淹禾,將CountDownTimer中復(fù)制到我們新的類中馁菜,小小修改下即可實現(xiàn)我們的效果~

package com.example.hlqcountdowntimer.weight;

import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.widget.TextView;

public abstract class CountDownTimer {

    private final TextView test;

    /**
     * Millis since epoch when alarm should stop.
     */
    private final long mMillisInFuture;

    /**
     * The interval in millis that the user receives callbacks
     */
    private final long mCountdownInterval;

    private long mStopTimeInFuture;

    /**
     * boolean representing if the timer was cancelled
     */
    private boolean mCancelled = false;

    /**
     * @param millisInFuture
     *            The number of millis in the future from the call to
     *            {@link #start()} until the countdown is done and
     *            {@link #onFinish()} is called.
     * @param countDownInterval
     *            The interval along the way to receive {@link #onTick(long)}
     *            callbacks.
     */
    public CountDownTimer(TextView test, long millisInFuture, long countDownInterval) {
        this.test = test;
        mMillisInFuture = millisInFuture;
        mCountdownInterval = countDownInterval;
    }

    /**
     * Cancel the countdown.
     */
    public synchronized final void cancel() {
        mCancelled = true;
        mHandler.removeMessages(MSG);
    }

    /**
     * Start the countdown.
     */
    public synchronized final CountDownTimer start() {
        mCancelled = false;
        if (mMillisInFuture <= 0) {
            onFinish();
            return this;
        }
        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
        mHandler.sendMessage(mHandler.obtainMessage(MSG));
        return this;
    }

    /**
     * Callback fired on regular interval.
     * 
     * @param millisUntilFinished
     *            The amount of time until finished.
     */
    public void onTick(long millisUntilFinished) {
        long time = millisUntilFinished / 1000;
        test.setText(time + "秒后可重發(fā)");
    };

    /**
     * Callback fired when the time is up.
     */
    public void onFinish() {
        test.setEnabled(true);
        test.setText("完犢子");
    };

    private static final int MSG = 1;

    // handles counting down
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {

            synchronized (CountDownTimer.this) {
                if (mCancelled) {
                    return;
                }

                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

                if (millisLeft <= 0) {
                    onFinish();
                }
                // else if (millisLeft < mCountdownInterval) {
                // // no tick, just delay until done
                // sendMessageDelayed(obtainMessage(MSG), millisLeft);
                // }
                else {
                    long lastTickStart = SystemClock.elapsedRealtime();
                    onTick(millisLeft);

                    // take into account user's onTick taking time to execute
                    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();

                    // special case: user's onTick took more than interval to
                    // complete, skip to next interval
                    while (delay < 0)
                        delay += mCountdownInterval;

                    sendMessageDelayed(obtainMessage(MSG), delay);
                }
            }
        }
    };
}

調(diào)用的時候需要傳遞當(dāng)前TextView,計時毫秒數(shù)以及調(diào)用間隔毫秒數(shù)即可铃岔,如下:

new com.example.hlqcountdowntimer.weight.CountDownTimer(test, 10000, 1000) {
}.start();

來個圖瞅瞅~
這里寫圖片描述

源碼奉上~

下載地址:http://download.csdn.net/detail/u012400885/9752225

感謝大家觀看~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末火邓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子德撬,更是在濱河造成了極大的恐慌铲咨,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜓洪,死亡現(xiàn)場離奇詭異纤勒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)隆檀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門摇天,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恐仑,你說我怎么就攤上這事泉坐。” “怎么了裳仆?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵腕让,是天一觀的道長。 經(jīng)常有香客問我歧斟,道長纯丸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任静袖,我火速辦了婚禮觉鼻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘队橙。我一直安慰自己坠陈,他們只是感情好萨惑,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仇矾,像睡著了一般咒钟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上若未,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天朱嘴,我揣著相機(jī)與錄音,去河邊找鬼粗合。 笑死萍嬉,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隙疚。 我是一名探鬼主播壤追,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼供屉!你這毒婦竟也來了行冰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤伶丐,失蹤者是張志新(化名)和其女友劉穎悼做,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哗魂,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡肛走,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了录别。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朽色。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖组题,靈堂內(nèi)的尸體忽然破棺而出葫男,到底是詐尸還是另有隱情,我是刑警寧澤崔列,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布梢褐,位于F島的核電站,受9級特大地震影響峻呕,放射性物質(zhì)發(fā)生泄漏利职。R本人自食惡果不足惜趣效,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一瘦癌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧跷敬,春花似錦讯私、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桶癣。三九已至,卻和暖如春娘锁,著一層夾襖步出監(jiān)牢的瞬間牙寞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工莫秆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留间雀,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓镊屎,卻偏偏與公主長得像惹挟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缝驳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容