-
-
Save bverc/1492672 to your computer and use it in GitHub Desktop.
/* | |
* 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.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 long mPauseTime; | |
private boolean mCancelled = false; | |
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; | |
} | |
/** | |
* 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. | |
*/ | |
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 (!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); | |
} | |
} | |
} | |
} | |
} | |
}; | |
} |
Nice implementation
Great! Thanks a lot! It's the simpliest way to replace stock timers without any hard work.
This is so excellent. Simple and great implementation !! This actually deserves to be added to actual Android classes.
Hi, it seems like if the mCountDownInterval
is long enough and pause()
and resume()
calls come consecutively from a client in between two adjacent ticks, too frequent onTick()
callbacks will be scheduled incorrectly.
Example scenario: with interval = 100; onTick()
at t0
, MSG scheduled for t100
. A client using this timer instance calls pause()
and resume()
at t40
and t60
respectively. Currently, resume()
will schedule a MSG immediately and onTick()
is called (at ~t60
) and schedule another for t160
. The previously scheduled MSG will also be handled and fire another tick at t100
and schedule another for t200
and so on. So, effectively, ticks are received every 60 and 40 ms. Also, there will be two onFinished()
calls. More the pair of pause, resume calls between adjacent ticks, more the ticks scheduled, since the handler messages are only started and never stopped.
Looks like adding mHandler.removeMessages(MSG)
to pause()
should fix this.
Great work...thx mate..
I do have a question: what if you need to use a specific timezone ?? How should I go about with it ?...
My app uses the Canadian timezone (Montreal to be precise....)
Hi, it seems like if the
mCountDownInterval
is long enough andpause()
andresume()
calls come consecutively from a client in between two adjacent ticks, too frequentonTick()
callbacks will be scheduled incorrectly.Example scenario: with interval = 100;
onTick()
att0
, MSG scheduled fort100
. A client using this timer instance callspause()
andresume()
att40
andt60
respectively. Currently,resume()
will schedule a MSG immediately andonTick()
is called (at ~t60
) and schedule another fort160
. The previously scheduled MSG will also be handled and fire another tick att100
and schedule another fort200
and so on. So, effectively, ticks are received every 60 and 40 ms. Also, there will be twoonFinished()
calls. More the pair of pause, resume calls between adjacent ticks, more the ticks scheduled, since the handler messages are only started and never stopped.Looks like adding
mHandler.removeMessages(MSG)
topause()
should fix this.
That made the trick!! Thanks a lot!!
Thanks a lot! You saved my time, effort and energy.
Great Help, Thanks a lot.
It's working fine, but I'm getting this warning where you handles countdown : this handler class should be static or leaks might occur (anonymous android.os.handler)
It's working fine, but I'm getting this warning where you handles countdown : this handler class should be static or leaks might occur (anonymous android.os.handler)
try this:
// handles counting down
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@OverRide
public void handleMessage(Message msg) {
synchronized (CountDownTimerWithPause.this) {
long millisLeft = timeLeft();
if (millisLeft <= 0) {
cancel();
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 = mCountdownInterval - (SystemClock.elapsedRealtime() - lastTickStart);
// special case: user's onTick took more than mCountdownInterval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
So, is it possible to add a delay before every resume ?
So what should we do if we want to add a delay before the time starts to decrease ?
Thanks :D