Reputation: 1047
I'm in the process of designing a chronometer / countdown timer app for Android 2.2 and would like one button press to start both the chronometer and the timer simultaneously. So, ideally, I'd like the seconds (time) on both the chronometer and timer to change at the same instance. (The timer will be counting down even as the chronometer is counting up). Since I'm using the chronometer and timer functionality provided by Android, I wrote the following piece of code when the user presses the 'Start' button
private boolean mStartPressedOnce = false;
long mTimeWhenStopped = 0;
Chronometer mChronometer;
MyCounter mCounter;
...
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.StartButton:
// Perform some initialization for the chronometer depending
// on the button press
if (mStartPressedOnce == false) {
mChronometer.setBase(SystemClock.elapsedRealtime());
} else {
mChronometer.setBase(SystemClock.elapsedRealtime() + mTimeWhenStopped);
}
// Perform the initialization for the timer
mCounter = new MyCount(45000, 1000);
// Fire up the chronometer
mChronometer.start();
// Fire up the timer
mCounter.start();
break;
case R.id.ResetButton:
// Reset the chronometer
mChronometer.setBase(SystemClock.elapsedRealtime());
mTimeWhenStopped = 0;
break;
case case R.id.StopButton:
mStartPressedOnce = true;
// Stop the chronometer
mTimeWhenStopped = mChronometer.getBase() - SystemClock.elapsedRealtime();
mChronometer.stop();
break;
}
...
public class MyCounter extends CountDownTimer {
@Override
public MyCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onFinish() {
// Nothing to do here
}
@Override
public void onTick(long millisUntilFinished) {
long seconds = (long) (millisUntilFinished / 1000);
long minutes = (long) ((millisUntilFinished / 1000) / 60);
long hours = (long) (((millisUntilFinished / 1000) / 60) / 60);
// Do some formatting to make seconds, minutes and hours look pretty
// Update the timer TextView
(TextView) findViewById(R.id.CountDownTimerTextView))
.setText(hours + ":" + minutes + ":" + seconds);
}
}
Though it looks like the seconds on the chronometer and timer are in sync initially, after a short time, they seem to go off and the second updates for both occur at different times.
Was wondering what I could do to fix this. I did come across - and read this thread
Running multiple AsyncTasks at the same time -- not possible?
I realize that there may be a design change needed but I'm not sure exactly what needs to be done.
Edit: Included types for chronometer and timer and method for calculating time using Chronometer - per jolivier and njzk2's suggestions
Upvotes: 6
Views: 1393
Reputation: 1047
So, after mulling over this for some time and going off of the suggestion jolivier
so generously shared with us, I realized that there exists a method called onChronometerTick
which is called every time there is chronometer tick (every second, in this case). So, I thought of subtracting 1000 milliseconds from the counter every time the method is called and update the timer display accordingly. I got rid of the Android timer piece (CountDownTimer
) completely. I figured this would be a nice way to have both displays update at the same time. It's also a simple implementation of a timer.
I'm happy to report that it seems to work well. Both the timer and chronometer displays indeed update at the same time. So, the original question looks like it's answered. Unfortunately, I ran into an off-by-two error on the timer front that I fixed with an ugly hack. I'm posting what I have so far. Any suggestions on how to fix the hack or improve the code are welcome. Note that I have commented the code to try to make it easy to understand what's been done.
Edit for bug: One more thing I noticed is that after around 10 minutes or so the chronometer and timer are off by one second. More precisely, the timer is behind the chronometer by one second. Not yet sure why this happens.
private boolean mStartPressedOnce = false;
long mTimeWhenStopped = 0;
Chronometer mChronometer;
long millisUntilFinished = 0;
boolean firstPassOver = false;
int counter = 0;
...
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.StartButton:
// Perform some initialization for the chronometer depending
// on the button press
if (mStartPressedOnce == false) {
mChronometer.setBase(SystemClock.elapsedRealtime());
} else {
mChronometer.setBase(SystemClock.elapsedRealtime() + mTimeWhenStopped);
}
// Fire up the chronometer
mChronometer.start();
break;
case R.id.ResetButton:
// Reset the chronometer
mChronometer.setBase(SystemClock.elapsedRealtime());
mTimeWhenStopped = 0;
break;
case case R.id.StopButton:
mStartPressedOnce = true;
// Stop the chronometer
mTimeWhenStopped = mChronometer.getBase() - SystemClock.elapsedRealtime();
mChronometer.stop();
break;
}
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.stop_watch);
mChronometer = (Chronometer) findViewById(R.id.StopWatchTextView);
// Initialize the number of milliseconds before the timer expires (
// set the timer) - in this case to 46 seconds
millisUntilFinished = 46000;
// Display how many seconds remain before the timer expires
((TextView) findViewById(R.id.CountDownTimerTextView)).setText(hours
+ ":" + minutes + ":" + millisUntilFinished / 1000);
// In line with the suggestion provided by jolivier - make the timer
// the slave and update its display every time the chronometer
// ticks
mChronometer
.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() {
@Override
public void onChronometerTick(Chronometer chronometer) {
// Update the display for the chronometer
CharSequence text = chronometer.getText();
chronometer.setText(text);
// Update the display for the timer
// !!! BUG !!!
// Looks like onChronometerTick is called at the 0th second
// and this causes an off by two error if a count down timer
// is being implemented. Fixed it with this hack. There's gotta
// be a more elegant solution, though.
if(counter >= 2) {
millisUntilFinished1 = millisUntilFinished1 - 1000;
counter = 2;
}
counter++;
if (millisUntilFinished >= 0) {
long seconds = (long) (millisUntilFinished / 1000);
long minutes = (long) ((millisUntilFinished / 1000) / 60);
long hours = (long) (((millisUntilFinished / 1000) / 60) / 60);
// Do some formatting to make seconds, minutes and hours look pretty
// Update the timer TextView
((TextView) findViewById(R.id.CountDownTimerTextView))
.setText(hours + ":" + minutes + ":"
+ seconds);
}
}
});
// Other code
...
}
Upvotes: 2
Reputation: 7635
You can retrieve the current time with System.currentTimeMillis(), store it into a variable and forward it to both mChronometer
and mCounter
, so that they use the same time reference although their task started at different time.
Edit: with the given types, the android documentation about Chronometer will tell you that you can use elapsedRealTime to achieve what I said.
CountDownTimer does not have this and its start
method is final so you may want to use another implementation, a better view of your use case might help us.
Basically, wanting two threads to perform an action at the same millisecond is never a good idea, one of them will serve as the clock and the other one must be a slave and listen to the clock.
Upvotes: 2