I'm making a simple timer in Android using a CountDownTimer and some NumberPickers. Right now when the user hits my button and the timer is not going it will start, when the hit the same button and timer is counting down it should stop. My problem is that it is not stopping. I call CountDownTimer.cancel() but this doesn't seem to do anything, the code in onTick() still continues to get executed, meaning the NumberPickers are still getting decremented as if the timer is still running.
I'm not sure if it is my code, or that I'm just using the CountDownTimer incorrectly. So when I call the Cancel() method should the onTick() events stop getting triggered? They don't seem to be so how can I do this?
I'll put some of my code here:
Code for my button:
#Override
public void onClick(View v) {
//Calculate total time from NumberPickers in seconds
long startTime = (numberPicker1.getValue() * 60) * 1000 + numberPicker2.getValue() * 1000;
Log.i(TAG, "Start time: " + startTime + "");
//Create CountDownTimer with values from NumberPickers
countDownTimer = new MyCountDownTimer(startTime, interval);
text.setText(text.getText() + String.valueOf(startTime / 1000)); //should be removed
if(!timerHasStarted) {
countDownTimer.start();
timerHasStarted = true;
//Disable the NumberPickers after 'Start' is pressed
numberPicker1.setEnabled(false);
numberPicker2.setEnabled(false);
startButton.setText(R.string.stop);
} else {
countDownTimer.cancel();
//countDownTimer = countDownTimer.pause();
timerHasStarted = false;
//Re-enable the NumberPickers after 'Start' is pressed
numberPicker1.setEnabled(true);
numberPicker2.setEnabled(true);
startButton.setText(R.string.restart);
}
}
onTick() code for my timer:
#Override
public void onTick(long millisUntilFinished) {
text.setText("" + millisUntilFinished / 1000);
//Decrement the NumberPickers after each second
if(numberPicker2.getValue() > 0) {
//When seconds left is greater than 0 decrement seconds
changeValueByOne(numberPicker2, false);
}
else if(numberPicker1.getValue() > 0 && numberPicker2.getValue() <= 0) {
//Else, if seconds <= 0 and minutes > 0 decrement minutes and reset seconds to 59
changeValueByOne(numberPicker1, false);
numberPicker2.setValue(59);
}
else if(numberPicker1.getValue() <= 0 && numberPicker2.getValue() <= 0){
//Else, if both minutes and seconds <= 0 revert back to 0 and finish
//This should never really happen, but just in case
numberPicker1.setValue(0);
numberPicker2.setValue(0);
Log.i(TAG, "There is a tick when both are 0");
}
this.millisUntilFinished = millisUntilFinished;
}
Problem:
In your onClick() you are creating a new object of MyCountDownTimer for every click, without checking the value of timerHasStarted.
countDownTimer = new MyCountDownTimer(startTime, interval);
and then you are calling cancel() on this new object. Hence your countdown timer does not stop goes on.
Solution:
Create MyCountDownTimer object only if the value of timerHasStarted is false (in if block).
#Override
public void onClick(View v) {
//Calculate total time from NumberPickers in seconds
long startTime = (numberPicker1.getValue() * 60) * 1000 + numberPicker2.getValue() * 1000;
Log.i(TAG, "Start time: " + startTime + "");
if(!timerHasStarted) {
//Create CountDownTimer with values from NumberPickers
countDownTimer = new MyCountDownTimer(startTime, interval);
countDownTimer.start();
timerHasStarted = true;
//Disable the NumberPickers after 'Start' is pressed
numberPicker1.setEnabled(false);
numberPicker2.setEnabled(false);
startButton.setText(R.string.stop);
} else {
countDownTimer.cancel();
//countDownTimer = countDownTimer.pause();
timerHasStarted = false;
//Re-enable the NumberPickers after 'Start' is pressed
numberPicker1.setEnabled(true);
numberPicker2.setEnabled(true);
startButton.setText(R.string.restart);
}
}
Related
I am making some kind of Sale event app . For that i require a Recyclerview with each item having a countdown timer in it. Each items has countdown and it says "Sale begins in XX hr: YY min" And when the countdown of the item expires , the item should go down to the bottom of Recyclerview.
I have made a countdown timer inside onbindviewholder but i am getting error when the timer expires . Please someone help me overcome this... This is what is required img
The expiry time of each item is coming from Firestore database .
I am doing this in my onbindViewholder...
String remTime;
#Override
public void onBindViewHolder(#NonNull Holder holder, int position) {
ItemDet item = ItemArrayList.get(position);
long timer = item.getCategoryTime().toDate().getTime();
final long currentTime = System.currentTimeMillis();
long expiryTime = timer - currentTime;
if (holder.timer != null) {
holder.timer.cancel();
}
holder.timer=new CountDownTimer(expiryTime, 1000) {
public void onTick(long millisUntilFinished) {
long seconds = millisUntilFinished / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if(days==0){
remTime= minutes % 60 + "m:" + seconds % 60+"s";
}
if(days<1 && hours>0 && minutes>0 && seconds>0){
remTime= hours % 24 + "h:"+ minutes % 60 + "m";
}
if(days<1 && hours<1 && minutes>0 && seconds>0){
remTime= minutes % 60 + "m:"+ seconds % 60+"s";
}
if(days<1 && hours<1 && minutes<1 && seconds>0){
remTime= seconds % 60+"s";
}
if (days>0){
remTime= days+"d, " +hours % 24 + "h";
}
holder.item_time_h.setText(remTime);
}
public void onFinish() {
holder.itemView.setEnabled(false);
holder.item_time_h.setText("Time up!");
ItemArrayList.remove(holder.adapterPosition());
notifyItemRemoved(holder.adapterPosition());
notifyItemRangeChanged(holder.adapterPosition(), ItemArrayList.size());
//Here after timer finish i want to remove the item from the top and put it on bottom
}
Please help me in this, i am stuck since 2 weeks
This shouldn't be hard. When the timer ends, you should move the item to the end of the list and then notify the adapter about this.
public void onFinish() {
holder.itemView.setEnabled(false);
holder.item_time_h.setText("Time up!");
int oldPosition = holder.adapterPosition();
ItemDet item = ItemArrayList.remove(oldPosition);
ItemArrayList.add(item);
int newPosition = ItemArrayList.size() - 1;
notifyItemMoved(oldPosition, newPosition);
}
Hope I didn't make mistakes. I didn't test the code. Try this. I'm hoping for some feedback from you.
UPDATE
Now I see that your expiryTime depends on the System's time and it's not a fixed value. As the time passes, some of your Counters will finish counting. In that exact moment when the Counter finishes to count, if you would recompute expiryTime, its value will be 0. When you exit the fragment and reenter it, the fragment will get recreated, the recyclerview will get recreated and the expiryTime will get recomputed and it will be a negative value. And because of that the Counter's onFinish method will be called right away, when the recycler's layout is being created. A quick fix could be that to not start the timer if expiryTime is negative. For the moment I think you could do this:
final long currentTime = System.currentTimeMillis();
long expiryTime = timer - currentTime;
//ADD THIS HERE
if(expiryTime <= 0) {
return;
}
if (holder.timer != null) {
holder.timer.cancel();
}
So i have here Quiz App and have timer. So what i want to happen for example i have set the timer for 15 seconds and if the user answer the question in 5 seconds i want the 10 seconds ramaining become 10 points and it will add to previous score plus the score of you will get upon answering the questions. so for now i have this ...
if(savedInstanceState!=null){
//saved instance state data
int exScore = savedInstanceState.getInt("score");
scoreText.setText("Score: "+exScore);
}
Timer = new CountDownTimer(15000, 1000) {
#Override
public void onTick(long millisUntilFinished) {
tv_time.setText("" + millisUntilFinished / 1000);
int progress = (int) (millisUntilFinished / 150);
progressBar.setProgress(progress);
}
#Override
public void onFinish() {
progressBar.setProgress(0);
timeUp(context);
}
}.start();
and here is the one for onclick. if the user answer correctly it will add 10points automatically
public void onClick(View view) {
Button clicked = (Button) view;
int exScore = getScore();
if (clicked.getText().toString().equals(this.active_question.getAnswer()) ) {
if (this.questions.size() > 0) {
setQuestion(questions.poll());
scoreText.setText("Score: " + (exScore + 10))
} else {
CustomGameOver cdd = new CustomGameOver(PlayQuizActivity.this);
cdd.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
cdd.show();
setHighScore();
Timer.cancel();
}
}
I dont have any idea on how to get the remaianing time on CountdownTimer and add it as a score when the answer is correct. could anyone please help me please.
Just use millisUntilFinished from onTick of the CountDownTimer
And the bonus will be millisUntilFinished/1000
P.S I think you better use a lower interval than 1000, so the ProgressBar will seem smoother.
All you need to do is declare a long variable timeleft in your MainActivity.
long timeleft;
Then, when you create a new Timer, set the "onTick" override to update the timeleft variable each "onTick" (which in the following example is 1000 milliseconds )
timer = new CountDownTimer(time, 1000) {
#Override
public void onTick(long millisecondsUntilFinished) {
timeleft = millisecondsUntilFinished;
}
}
Your app can access then the variable timeleft every time you need to check how much time is left.
score = score + timeleft / 1000; // divide by 1000 to get seconds
Have in mind that if you need to update the timer, you have to cancel it and create a new timer with the updated time left (and the same override);
timeleft = timeleft + bonustime; // (if you want to add bonus time, remember has to be in milliseconds)
if( timer != null){ timer.cancel();} // better check first if the timer exists
timer = new CountDownTimer(timeleft, 1000) {
#Override
public void onTick(long millisecondsUntilFinished) {
timeleft = millisecondsUntilFinished;
}
I have an edit text for entering a time in minutes, a button to start a countdown timer using the minutes entered in the edit text which updates a text view, and a button which I want to cancel the countdown timer before it finishes.
The problems I'm having are...the variable the countdown timer needs (the milliseconds) can't get assigned until I click the start timer button because it's coming from an edit text. So I created the countdown timer inside of the onClick for the start timer button. However...I want another button to cancel the timer...and it doesn't have access to the countdown timer. So that didn't solve anything and I'm confused at how to do this. Any advice would help.
Edit: I just realized I could move the stop timer button with the listener to the start timer button onClick also...under the countdown timer. I'm just confused about how the countdown timer could find the milliseconds variable if I have countdown timer outside of the start timer onClick. I guess moving the stop timer under countdown timer works...but still feels like I'm missing something.
Edit2: I tried moving countDownTimer into onCreate and making milliseconds global...but I have to make countDownTimer final in order for the buttons to be able to use it, and countDownTimer just skips to onFinish.
mTimerTextView = (TextView)findViewById(R.id.timer_text_view);
mTimerEditText = (EditText)findViewById(R.id.timer_edit_text);
mStartTimerButton = (Button)findViewById(R.id.btn_start_timer);
mStartTimerButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Getting time in minutes from edit text
int timeEntered = Integer.parseInt(mTimerEditText.getText().toString());
long milliseconds = timeEntered * 60000;
CountDownTimer countDownTimer = new CountDownTimer(milliseconds, 1000) {
#Override
public void onTick(long millisUntilFinished) {
int seconds = (int) (millisUntilFinished / 1000) % 60;
int minutes = (int) ((millisUntilFinished / (1000*60)) % 60);
int hours = (int) ((millisUntilFinished / (1000*60*60)) % 24);
mTimerTextView.setText(String.format("%02d:%02d:%02d", hours, minutes, seconds));
}
#Override
public void onFinish() {
new AlertDialog.Builder(DailyGoalActivity.this)
.setTitle("Time's Up")
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}).show();
}
};
countDownTimer.start();
}
});
mStopTimerButton = (Button)findViewById(R.id.btn_stop_timer);
mStopTimerButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// countDownTimer.cancel(); <- How could I do this?
}
});
there are two possible reason for your problem first one is that you need to create countdown timer in onCreate and second one is that you are stopping null countdown timer.
solution-1 you can use a boolean variable to check that timer has start or not and then start timer accordingly on button click.Apart from this initialize timer in on create and start in on click follow this code please follow this link
solution-2 to stop the timer use this code
if(countDownTimer != null) {
countDownTimer .cancel();
countDownTimer = null;
}
Isn't the simple solution to declare CountDownTimer variable globally?
Move this code to onCreate
CountDownTimer countDownTimer = new CountDownTimer(milliseconds, 1000) {
#Override
public void onTick(long millisUntilFinished) {
int seconds = (int) (millisUntilFinished / 1000) % 60;
int minutes = (int) ((millisUntilFinished / (1000*60)) % 60);
int hours = (int) ((millisUntilFinished / (1000*60*60)) % 24);
mTimerTextView.setText(String.format("%02d:%02d:%02d", hours, minutes, seconds));
}
}
Then start timer in Start button's onClick and cancel in Stop button's onClick.
Hope it helps.
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
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.
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
...
}
I am trying to completely restart Chronometer and its does not work. Instead it is being paused. Basically what I am trying to do is to do something while chronometer is counting till 10. After its done we prompt the user to try again. In which case we want to redo the count from 1 to 10 sec. But the Chronometer starts from the paused time instead of starting 0.
here is the code:
_cutOfTime = 10; // constant
every time button is pressed do startRecording()
it should always initiate the Chronometer instead of stop/pause it, but it does the opposite
protected void startRecording(){
this._watch = (Chronometer) findViewById(R.id.chrono);
if (this._watch != null)
this._watch.setOnChronometerTickListener(new OnChronometerTickListener() {
#Override
public void onChronometerTick(Chronometer chronometer) {
long countUp = (SystemClock.elapsedRealtime() - chronometer.getBase()) / 1000;
Log.i(_tag, "time now: " + String.valueOf(countUp));
if(countUp > _cutOfTime)
{
Log.i(_tag, "stop recording!!: ");
_watch.stop();
stopRecordWav();
launchPromptWithResults();
}
long sec = countUp % 60;
String asText = "" + sec;
_textView.setText("Recorded: " + asText);
}
});
if (_watch != null)
_watch.start();
}
Is there a way to reset the chronometer so it does not pause but completely stop?
When I played with the chronometer awhile back I just used the setBase() method to set the base to the current time just before calling start(). Depending on your exact needs you may need to add some logic around whether to reset the chronometer or not before starting it.
View.OnClickListener mStartButtonListener = new OnClickListener() {
#Override
public void onClick(View arg0) {
mChronometer.setBase(SystemClock.elapsedRealtime());
mChronometer.start();
}
};