Android 倒计时CountDownTimer的使用和封装及改进

(一)介绍

官方文档。CountDownTimer是一个倒计时的类,还可以指定时间间隔定期通知,举个栗子,比如说你倒计时是100秒的,可以指定每20秒通知一次,这样开始的时候会回调一次,20秒时会回调一次,40秒时会回调一次…,200秒时的回调和时间间隔的回调不同方法的。

(二)使用

CountDownTimer只有一个构造,

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

从方法的注释中可以看出,第一个参数是总的倒计时时间,第二个参数是定期的回调时间。

使用的时候:

new CountDownTimer(10000, 2000) {
    @Override
    public void onTick(long millisUntilFinished) {
        Log.v("CountDownTimerTest", "onTick millisUntilFinished = " + millisUntilFinished);
    }
    @Override
    public void onFinish() {
        Log.v("CountDownTimerTest", "onFinish");
    }
}.start();

onTick()方法是定期间隔回调的方法,onFinish()就是结束时回调的方法了。

![运行结果截图](https://img-blog.csdn.net/20170305143654931?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2VraWFzb28=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)<br/>
可以看出在9970毫秒的时候回调了一次onTick()方法,因为消息传递会消耗一点时间,使用传过来时是9976毫秒,后面的也是,这里执行了onTick()方法4次,那剩下2秒的时候不应该还要执行一次么?怎么3964后就不执行onTick()方法了?原因是3964毫秒再经过2秒就剩下1900多毫秒,比2秒小,使用不执行onTick()方法。

CountDownTimer的核心代码

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);
    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
    while (delay < 0) delay += mCountdownInterval;
    sendMessageDelayed(obtainMessage(MSG), delay);
}

如果倒计时后不让它执行了也可以取消倒计时,调用CountDownTimer的cancel()方法就可以取消倒计时了。

(三)封装

有时候我们并不关心onTick(),只需要关心onFinish()方法的执行,我们可以封装个工具类,把这两个功能分开,想单独用时就单独用,想合起来用时就合起来用。

package com.ce.countdowntimertest.utils;
import android.os.CountDownTimer;
/**
 * 倒计时工具类
 */
public class CountDownTimerUtils {
    /**
     * 倒计时结束的回调接口
     */
    public interface FinishDelegate {
        void onFinish();
    }
    /**
     * 定期回调的接口
     */
    public interface TickDelegate {
        void onTick(long pMillisUntilFinished);
    }
    private final static long ONE_SECOND = 1000;
    /**
     * 总倒计时时间
     */
    private long mMillisInFuture = 0;
    /**
     * 定期回调的时间 必须大于0 否则会出现ANR
     */
    private long mCountDownInterval;
    /**
     * 倒计时结束的回调
     */
    private FinishDelegate mFinishDelegate;
    /**
     * 定期回调
     */
    private TickDelegate mTickDelegate;
    private MyCountDownTimer mCountDownTimer;
    /**
     * 获取 CountDownTimerUtils
     * @return CountDownTimerUtils
     */
    public static CountDownTimerUtils getCountDownTimer() {
        return new CountDownTimerUtils();
    }
    /**
     * 设置定期回调的时间 调用{@link #setTickDelegate(TickDelegate)}
     * @param pCountDownInterval 定期回调的时间 必须大于0
     * @return CountDownTimerUtils
     */
    public CountDownTimerUtils setCountDownInterval(long pCountDownInterval) {
        this.mCountDownInterval=pCountDownInterval;
        return this;
    }
    /**
     * 设置倒计时结束的回调
     * @param pFinishDelegate 倒计时结束的回调接口
     * @return CountDownTimerUtils
     */
    public CountDownTimerUtils setFinishDelegate(FinishDelegate pFinishDelegate) {
        this.mFinishDelegate=pFinishDelegate;
        return this;
    }
    /**
     * 设置总倒计时时间
     * @param pMillisInFuture 总倒计时时间
     * @return CountDownTimerUtils
     */
    public CountDownTimerUtils setMillisInFuture(long pMillisInFuture) {
        this.mMillisInFuture=pMillisInFuture;
        return this;
    }
    /**
     * 设置定期回调
     * @param pTickDelegate 定期回调接口
     * @return CountDownTimerUtils
     */
    public CountDownTimerUtils setTickDelegate(TickDelegate pTickDelegate) {
        this.mTickDelegate=pTickDelegate;
        return this;
    }
    public void create() {
        if (mCountDownTimer != null) {
            mCountDownTimer.cancel();
            mCountDownTimer = null;
        }
        if (mCountDownInterval <= 0) {
            mCountDownInterval = mMillisInFuture + ONE_SECOND;
        }
        mCountDownTimer = new MyCountDownTimer(mMillisInFuture, mCountDownInterval);
        mCountDownTimer.setTickDelegate(mTickDelegate);
        mCountDownTimer.setFinishDelegate(mFinishDelegate);
    }
    /**
     * 开始倒计时
     */
    public void start() {
        if (mCountDownTimer == null) {
            create();
        }
        mCountDownTimer.start();
    }
    /**
     * 取消倒计时
     */
    public void cancel() {
        if (mCountDownTimer != null) {
            mCountDownTimer.cancel();
        }
    }
    private static class MyCountDownTimer extends CountDownTimer {
        private FinishDelegate mFinishDelegate;
        private TickDelegate mTickDelegate;
        /**
         * @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 MyCountDownTimer(long millisInFuture, long countDownInterval) {
            super(millisInFuture, countDownInterval);
        }
        @Override
        public void onTick(long millisUntilFinished) {
            if (mTickDelegate != null) {
                mTickDelegate.onTick(millisUntilFinished);
            }
        }
        @Override
        public void onFinish() {
            if (mFinishDelegate != null) {
                mFinishDelegate.onFinish();
            }
        }
        void setFinishDelegate(FinishDelegate pFinishDelegate) {
            this.mFinishDelegate=pFinishDelegate;
        }
        void setTickDelegate(TickDelegate pTickDelegate) {
            this.mTickDelegate=pTickDelegate;
        }
    }
}

使用也简单,用getCountDownTimer()方法获取实例;setMillisInFuture()方法是设置总倒计时时间;setFinishDelegate()方法是设置倒计时完成的回调;setCountDownInterval()方法是设置定期回调的时间间隔,但值要大于0,不然会出现ANR的,因为不停的回调会导致Looper消息处理不过来;setTickDelegate()方法是设置定期回调的。
只需要最终倒计时的使用:

Log.v("CountDownTimerTest", "Start");
        CountDownTimerUtils.getCountDownTimer()
                .setMillisInFuture(5000)
                .setFinishDelegate(new CountDownTimerUtils.FinishDelegate() {
                    @Override
                    public void onFinish() {
                        Log.v("CountDownTimerTest", "onFinish");
                    }
                }).start();

运行结果截图

也可以只要定期的间隔回调,不过要设置设置下总倒计时时间:

Log.v("CountDownTimerTest", "Start");
        CountDownTimerUtils.getCountDownTimer()
                .setMillisInFuture(10000)
                .setCountDownInterval(2000)
                .setTickDelegate(new CountDownTimerUtils.TickDelegate() {
                    @Override
                    public void onTick(long pMillisUntilFinished) {
                        Log.v("CountDownTimerTest", "pMillisUntilFinished = " + pMillisUntilFinished);
                    }
                }).start();

Android 倒计时CountDownTimer的使用和封装及改进_第1张图片

当然也可以两个都有:

Log.v("CountDownTimerTest", "Start");
        CountDownTimerUtils.getCountDownTimer()
                .setMillisInFuture(10000)
                .setCountDownInterval(2000)
                .setTickDelegate(new CountDownTimerUtils.TickDelegate() {
                    @Override
                    public void onTick(long pMillisUntilFinished) {
                        Log.v("CountDownTimerTest", "pMillisUntilFinished = " + pMillisUntilFinished);
                    }
                })
                .setFinishDelegate(new CountDownTimerUtils.FinishDelegate() {
                    @Override
                    public void onFinish() {
                        Log.v("CountDownTimerTest", "onFinish");
                    }
                }).start();      

Android 倒计时CountDownTimer的使用和封装及改进_第2张图片

(四)改进

虽然CountDownTimer很好用,但有个缺点,就是只能在主线程中运行,如果在子线程中运行就会报错,

new Thread(new Runnable() {
            @Override
            public void run() {
                Log.v("CountDownTimerTest", "SubThread Start");
                new android.os.CountDownTimer(10000, 2000) {
                    @Override
                    public void onTick(long millisUntilFinished) {
                        Log.v("CountDownTimerTest", "onTick millisUntilFinished = " + millisUntilFinished);
                    }

                    @Override
                    public void onFinish() {
                        Log.v("CountDownTimerTest", "onFinish");
                    }
                }.start();
            }
        }).start();

Android 倒计时CountDownTimer的使用和封装及改进_第3张图片

原因是在主线程中Handler的Looper Android系统已经在framework层帮我们prepare(prepareMainLooper())过了,但在我们这个子线程没有Looper,那怎么办?我们可以借助HandlerThread完成,新建个自己的CountDownTimer类,把Android的CountDownTimer的源代码复制到我们新建的CountDownTimer类中,修改下包名,就不要用Android的CountDownTimer这个类的了,改造如下:

package com.ce.countdowntimertest.common;

/*
 * 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.
 */

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;

/**
 *
 * Created by CE
 *
 * 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:
 *
 * 
 * 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 {@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; if (!isMainThread()) { mHandlerThread = new HandlerThread("CountDownTimerThread"); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper(), mCallback); } else { mHandler = new Handler(mCallback); } } /** * 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; } private boolean isMainThread() { return Looper.getMainLooper().getThread().equals(Thread.currentThread()); } /** * 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 android.os.Handler mHandler = new android.os.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); } } } };*/ private HandlerThread mHandlerThread; private Handler mHandler; private Handler.Callback mCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { synchronized (CountDownTimer.this) { if (mCancelled) { return true; } final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime(); if (millisLeft <= 0) { onFinish(); if (mHandlerThread != null) mHandlerThread.quit(); } else if (millisLeft < mCountdownInterval) { // no tick, just delay until done mHandler.sendMessageDelayed(mHandler.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; mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG), delay); } } return false; } }; }

如果是主线程就不用管了,如果是子线程我们就把HandlerThread的getLooper()拿出来给Handler。这里我用的是Handler.Callback来进行对消息处理的。现在我们把刚才在子线程中运行的CountDownTimer类改成我们自己定义的CountDownTimer类在运行一下:

new Thread(new Runnable() {
    @Override
    public void run() {
        Log.v("CountDownTimerTest", "SubThread Start");
        new com.ce.countdowntimertest.common.CountDownTimer(10000, 2000) {
            @Override
            public void onTick(long millisUntilFinished) {
                Log.v("CountDownTimerTest", "onTick millisUntilFinished = " + millisUntilFinished);
            }

            @Override
            public void onFinish() {
                Log.v("CountDownTimerTest", "onFinish");
            }
        }.start();
    }
}).start();

Android 倒计时CountDownTimer的使用和封装及改进_第4张图片

可以运行了,当然在主线程也是正常的:

Log.v("CountDownTimerTest", "Start");
        new com.ce.countdowntimertest.common.CountDownTimer(10000, 2000) {
            @Override
            public void onTick(long millisUntilFinished) {
                Log.v("CountDownTimerTest", "onTick millisUntilFinished = " + millisUntilFinished);
            }

            @Override
            public void onFinish() {
                Log.v("CountDownTimerTest", "onFinish");
            }
        }.start();

Android 倒计时CountDownTimer的使用和封装及改进_第5张图片

如果在CountDownTimerUtils类里想用这个改进的CountDownTimer,改下CountDownTimer的包名就可以运行。

源码地址

你可能感兴趣的:(Android,开发)