Android之实现倒计时的那点事儿

前言

LZ-Says:今天收到转正通知了,内心也就12s开心,随之而来的就是不平不淡。16年10月18号入职,感觉一切都好像眨眼之间,过得好快,各种忙
答应了自己,要好好努力。答应了家人,要好好奋斗。答应了心,要好好坚持。有什么理由不去努力

开始正题

好吧,今天一起回顾下,关于Android中实现倒计时功能吧~

今天为大家介绍俩种方式,都可以实现倒计时功能。这俩种方式分别是:

  1. Handler+Thread
  2. CountDownTimer

想必大家对于第一种实现方式肯定不会陌生了,简直So easy那再次回顾下第一种写法

1.通过使用Handler+Thread实现倒计时

首先编写布局文件

布局文件很简单,就是一个TextView,默认显示Handler获取验证码,点击TextView,进行倒计时操作,完成后恢复默认显示。

<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,实现效果~
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) {
                // 倒计时结束让按钮可用
                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实现倒计时

CountDownTimer简介

CountDownTimer是Android内部封装好的一个关于实现倒计时功能的类。所在包:package android.os;其内部实现也是通过咱第一种实现方式,没啥好说的,看看人家官方简介吧

官方使用方式
 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去实现倒计时,需要如下几个步骤:

  1. 实例化CountDownTimer对象;
  2. 提供计时时间毫秒以及时间间隔毫秒;
  3. 重写onTick()和onFinish()方法;
    那么这俩个方法分别都是什么作用呢?
    3.1 onTick(long millisUntilFinished)
    参数millisUntilFinished是倒计时的剩余时间。在倒计时结束后会调用onFinish。
    3.2 onFinish()
    倒计时结束后需要执行的操作可以写在这里。
  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) {
                // 倒计时结束让按钮可用
                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 + "秒后可重发");
                onFinish();
            }
            tvShowC.setText(time + "秒后可重发");
        }

        @Override
        public void onFinish() {
            tvShowC.setEnabled(true);
            tvShowC.setText("CountDownTimer获取验证码");
        }
    };

}

来个效果图~
这里写图片描述

不知道大家有没有发现一个小问题,怎么到1秒时,会出现短暂延迟?而且使用CountDownTimer可以显示0秒么?

关于以上问题,让我们一起去看看人家是什么写的,从他们写的代码中看看能不能发现相关蛛丝马迹关于可以显示0秒么这个问题,个人觉得,那必须啊就看怎么改他了~

深入了解CountDownTimer

让我们一起进入它内部去瞅瞅~

//首先就是相关的介绍,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去实现倒计时效果
/**
 * 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分析

首先从上面可以看出,一上来他就给我们进行一些简单的介绍,之后就是提供使用方法,接下来就是重点,让我们瞅瞅他们是什么写的~

  1. 方法使用synchronized修饰,保证一次操作只能有一个进行访问;
  2. 在文章开头,我简单说过他内部同样是通过Handler去实现倒计时效果,但是我们发现他使用了一个SystemClock.elapsedRealtime(),那么这个东西又是什么呢?经过百度后得知,他的作用就是返回系统启动到现在的毫秒数,包含休眠时间。不难理解,其实个人觉得和我们第一种写法差不多。
  3. 那么问题他为什么会出现短暂卡顿呢?其实大家在仔细查阅后会发现,当它等于1时,接下来再走不就是0了么,小于等于0的时候同样也会走一次,但是这次却不会更新UI,所以造成一种假象,就会让我们觉得界面出现了稍微卡顿。那么说到这,大家也就知道了怎么使用CountDownTimer去显示0.下面请看修改后的CountDownTimer~

改造后的CountDownTimer

基于以上分析,我们明白,只需要当计时毫秒数小于等于0的时候,他不会进行更新UI操作,那么我们只需要让它在小于等于0的时候,进行更新UI操作即可。

修改CountDownTimer

这部分很简单,创建一个类,将CountDownTimer中复制到我们新的类中,小小修改下即可实现我们的效果~

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 + "秒后可重发");
    };

    /**
     * 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);
                }
            }
        }
    };
}

调用的时候需要传递当前TextView,计时毫秒数以及调用间隔毫秒数即可,如下:

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

来个图瞅瞅~
这里写图片描述

源码奉上~

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

感谢大家观看~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容