Skip to content

Instantly share code, notes, and snippets.

@Gautier
Created December 12, 2010 00:58
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save Gautier/737759 to your computer and use it in GitHub Desktop.
Save Gautier/737759 to your computer and use it in GitHub Desktop.
Drop-in alternative for the Android CountDownTimer class, but which you can cancel from within onTick.
/*
* 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 alt.android.os;
import android.util.Log;
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 final long mMillisInFuture;
/**
* The interval in millis that the user receives callbacks
*/
private final long mCountdownInterval;
private long mStopTimeInFuture;
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.
*
* 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;
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) {
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);
}
}
}
}
};
}
@mircobabini
Copy link

You just saved my life. Awesome.

@nadavati
Copy link

nadavati commented Aug 5, 2013

good one:-)

@UmarIqbal
Copy link

Very nice!

@idemax
Copy link

idemax commented Mar 21, 2014

You just saved my life. Awesome. (2)

@mkeremkeskin
Copy link

Thanks!

@ag-chirag
Copy link

Why does it stop just after few seconds?

@ashmeh6
Copy link

ashmeh6 commented Feb 2, 2016

Thanks a lot!

@mikeyaworski
Copy link

This was super helpful. Thanks a lot.

@mmpfwl
Copy link

mmpfwl commented Mar 14, 2016

thanks , you are a good man!

@matghazaryan
Copy link

It's working... but sometimes onFinish not calling in background. What problem it can be?

@hiepth1988
Copy link

great, thanks you so much

@bonyhardian
Copy link

How to pause?

@sinandizdarevic
Copy link

This doesn't work on OnePlus 5T - Android 9.0 and it has the same problem.

@ELBAHLOULZIAD
Copy link

how can i call cancel from Main?

@javadghane
Copy link

javadghane commented Apr 8, 2020

you saved my time. thanks

@DharaGrubbrr
Copy link

Great, thank you so much

@shivamgup0001
Copy link

I want a timer to be decreased by 5s if wrong ans is provided how to do that????

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment