-
-
Save gb96/3c84f87eacd2c7b032cce299e338758f to your computer and use it in GitHub Desktop.
Drop-in alternative for the Android CountDownTimer class, but [1] can cancel from within onTick, [2] can pause() and resume(), [3] can restart(long millisInFuture, long countDownInterval), [4] isCancelled() and isPaused(), [5] config(long, long)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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. | |
* | |
* Modifications | |
* | |
* 2019-09-17 https://gist.github.com/gb96 restart(), restart(long, long), isCancelled(), isPaused(), config(long, long) | |
* 2012-07-01 https://gist.github.com/bverc onPause(), onResume() | |
* 2010-12-12 https://gist.github.com/Gautier can cancel() within onTick() | |
*/ | |
package alt.android.os; | |
import android.annotation.SuppressLint; | |
import android.os.Handler; | |
import android.os.SystemClock; | |
import android.os.Message; | |
/** | |
* 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 long mMillisInFuture; | |
/** | |
* The interval in millis that the user receives callbacks | |
*/ | |
private long mCountdownInterval; | |
private long mStopTimeInFuture; | |
private long mPauseTime; | |
private boolean mCancelled = true; | |
private boolean mPaused = 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; | |
} | |
/** | |
* Configure the countdown with new parameters but do not start it yet. | |
* | |
* | |
* Do not call it from inside CountDownTimer threads | |
* @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 config(long millisInFuture, long countDownInterval) { | |
cancel(); | |
mMillisInFuture = millisInFuture; | |
mCountdownInterval = countDownInterval; | |
return this; | |
} | |
/** | |
* Restart the countdown with new parameters. | |
* | |
* | |
* Do not call it from inside CountDownTimer threads | |
* @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 restart(long millisInFuture, long countDownInterval) { | |
return config(millisInFuture, countDownInterval).start(); | |
} | |
/** | |
* Restart the countdown with original parameters. | |
* | |
* Do not call it from inside CountDownTimer threads | |
*/ | |
public CountDownTimer restart() { | |
cancel(); | |
return start(); | |
} | |
/** | |
* Cancel the countdown. | |
* | |
* Do not call it from inside CountDownTimer threads | |
*/ | |
public final void cancel() { | |
mHandler.removeMessages(MSG); | |
mCancelled = true; | |
} | |
/** | |
* Start the countdown. | |
*/ | |
public synchronized final CountDownTimer start() { | |
if (mMillisInFuture <= 0) { | |
onFinish(); | |
return this; | |
} | |
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture; | |
mHandler.sendMessage(mHandler.obtainMessage(MSG)); | |
mCancelled = false; | |
mPaused = false; | |
return this; | |
} | |
/** | |
* Pause the countdown. | |
*/ | |
public long pause() { | |
mPauseTime = mStopTimeInFuture - SystemClock.elapsedRealtime(); | |
mPaused = true; | |
return mPauseTime; | |
} | |
/** | |
* Resume the countdown. | |
*/ | |
public long resume() { | |
mStopTimeInFuture = mPauseTime + SystemClock.elapsedRealtime(); | |
mPaused = false; | |
mHandler.sendMessage(mHandler.obtainMessage(MSG)); | |
return mPauseTime; | |
} | |
/** | |
* Callback fired on regular interval. | |
* @param millisUntilFinished The amount of time until finished. | |
*/ | |
protected abstract void onTick(long millisUntilFinished); | |
/** | |
* Callback fired when the time is up. | |
*/ | |
protected abstract void onFinish(); | |
private static final int MSG = 1; | |
// handles counting down | |
@SuppressLint("HandlerLeak") | |
private Handler mHandler = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
synchronized (CountDownTimer.this) { | |
if (!mPaused) { | |
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; | |
if (!mCancelled) { | |
sendMessageDelayed(obtainMessage(MSG), delay); | |
} | |
} | |
} | |
} | |
} | |
}; | |
public boolean isCancelled() { | |
return mCancelled; | |
} | |
public boolean isPaused() { | |
return mPaused; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment