Why does my CountDownTimer seem to be going backward? - java

I'm trying to make an app that reminds you to charge your phone if it is not used for a while. It works like this: you enter how long the phone should be idle before it reminds you. Then it starts a timer and reminds you when it finishes.
Here's my MainActivity.Java:
public class MainActivity extends AppCompatActivity {
//Defining UI elements
public Button changeAppStateButton;
public TextView minsEditText;
//App variables
boolean isAppRunning = false;
public int secondsPhoneIsAsleep;
public int timerDuration = secondsPhoneIsAsleep * 1000; //multiplying seconds by 100 to get milliseconds
public int tickDuration = 60000; //multiplying seconds (1) by 100 to get milliseconds
//Called when button is pressed
public void changeAppState(View view) {
Button changeAppStateButton = (Button) view;
if (isAppRunning) { //If the app is running, stop app
isAppRunning = false;
changeAppStateButton.setBackgroundColor(getColor(R.color.colorPurple));
changeAppStateButton.setText("Start Reminder");
timer.cancel();
Log.i("TIMER", "Timer interrupted");
} else { //If the app is not running, start app
secondsPhoneIsAsleep = Integer.parseInt(minsEditText.getText().toString()) * 60;
isAppRunning = true;
changeAppStateButton.setBackgroundColor(getColor(R.color.colorRed));
changeAppStateButton.setText("Stop Reminder");
timer.start();
Log.i("TIMER", "Timer started");
}
}
public CountDownTimer timer = new CountDownTimer(timerDuration, tickDuration) {
#Override
public void onTick(long millisUntilFinished) {
Log.i("TIMER", "tick");
}
#Override
public void onFinish() {
isAppRunning = false;
changeAppStateButton.setBackgroundColor(getColor(R.color.colorPurple));
changeAppStateButton.setText("Start Reminder");
Log.i("TIMER", "Timer finished");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Setting values for UI elements
changeAppStateButton = findViewById(R.id.changeAppStateButton);
minsEditText = findViewById(R.id.minEditText);
}
This is part of my XML:
<EditText
android:id="#+id/minEditText"
android:layout_width="59dp"
android:layout_height="42dp"
android:layout_marginTop="14dp"
android:ems="10"
android:foregroundTint="#FF0000"
android:inputType="number"
android:text="30"
android:textAlignment="center"
android:textColor="#000000"
android:textSize="20sp"
app:layout_constraintStart_toEndOf="#+id/whenUntouched"
app:layout_constraintTop_toBottomOf="#+id/numberEditText" />
<Button
android:id="#+id/changeAppStateButton"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="110dp"
android:background="#9C27B0"
android:fontFamily="#font/alegreya_sans_sc"
android:onClick="changeAppState"
android:text="Start Reminder"
android:textAlignment="center"
android:textColor="#FFFFFF"
android:textSize="25sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
This is logcat when I press the button:
I/DEV_TIMER: Timer finished
I/DEV_TIMER: Timer started
Why does the timer seem to be going backward? Why isn't it logging a message every "tick"?
Some explanation would be highly appreciated. I'm not too experienced with Android and Java.

Since the code specified in MainActivity.java
Doesn't initialize variable secondsPhoneIsAsleep, so the default value will be 0.
So timerDuration will be 0.
So timer is created to count for a duration of 0.
So when the button is clicked, even though you read new value for secondsPhoneIsAsleep, invoking timer.start() will cause it to count only till 0 based on the earlier initialized value.
Hence onFinish() gets called logging Timer finished, then the Timer started gets logged as part of button click code.
Solution
If you create timer instance on button click then it should use the correct value of secondsPhoneIsAsleep and work properly. Like below:
MainActvity.java
public class MainActivity extends AppCompatActivity {
//Defining UI elements
public Button changeAppStateButton;
public TextView minsEditText;
//App variables
boolean isAppRunning = false;
public int secondsPhoneIsAsleep;
public CountDownTimer timer;
public int timerDuration;
public int tickDuration = 1000; //multiplying 1 second by 1000 to get milliseconds
//Called when button is pressed
public void changeAppState(View view) {
Button changeAppStateButton = (Button) view;
if (isAppRunning) { //If the app is running, stop app
isAppRunning = false;
changeAppStateButton.setBackgroundColor(getColor(R.color.colorPurple));
changeAppStateButton.setText("Start Reminder");
timer.cancel();
Log.i("TIMER", "Timer interrupted");
} else { //If the app is not running, start app
secondsPhoneIsAsleep = Integer.parseInt(minsEditText.getText().toString()) * 60;
timerDuration = secondsPhoneIsAsleep * 1000;
timer = getNewTimer(); // Creates a new timer.
isAppRunning = true;
changeAppStateButton.setBackgroundColor(getColor(R.color.colorRed));
changeAppStateButton.setText("Stop Reminder");
timer.start();
Log.i("TIMER", "Timer started");
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Setting values for UI elements
changeAppStateButton = findViewById(R.id.changeAppStateButton);
minsEditText = findViewById(R.id.minEditText);
}
private CountdownTimer getNewTimer() {
return new CountDownTimer(timerDuration, tickDuration) {
#Override
public void onTick(long millisUntilFinished) {
Log.i("TIMER", "tick");
}
#Override
public void onFinish() {
isAppRunning = false;
changeAppStateButton.setBackgroundColor(getColor(R.color.colorPurple));
changeAppStateButton.setText("Start Reminder");
Log.i("TIMER", "Timer finished");
}
};
}

Related

How can I control the media player with service?

I am new to coding and at this point, I made a media player that starts and pauses the audio, has a working seekbar, duration, etc. Now I faced a big issue. The audio can't be played in the background and I found out that I can do this with service but this changes things. I read all kinds of topics on how to control the audio in service with seekbar and all kinds of stuff but nothing helped me. The main problem I face is having the seekbar to control the audio and a text to read the time. If there is someone to help me find a code for this it would be much appreciated.
The layout:
<SeekBar
android:id="#+id/seekBarMusic1"
android:layout_width="match_parent"
android:layout_height="#dimen/_20sdp"
android:layout_marginTop="#dimen/_12sdp"
android:layout_marginLeft="#dimen/_20sdp"
android:layout_marginRight="#dimen/_20sdp"
app:layout_constraintTop_toBottomOf="#+id/cardView"
tools:layout_editor_absoluteX="10dp" />
<TextView
android:id="#+id/playerPositionMusic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textColor="#color/remain_black"
android:textSize="25dp"
android:layout_marginBottom="#dimen/_80sdp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="#+id/cardView"
app:layout_constraintTop_toBottomOf="#+id/seekBarMusic1" />
<TextView
android:id="#+id/text_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/_80sdp"
android:text="15:00"
android:textColor="#color/remain_black"
android:textSize="25dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/cardView"
app:layout_constraintTop_toBottomOf="#+id/seekBarMusic1" />
<ImageView
android:id="#+id/btPlayMusic"
android:layout_width="80dp"
android:layout_height="80dp"
android:alpha="0.9"
android:background="#drawable/play"
android:theme="#style/Button.White"
app:layout_constraintEnd_toStartOf="#+id/text_time"
app:layout_constraintStart_toEndOf="#+id/playerPositionMusic"
app:layout_constraintTop_toBottomOf="#+id/seekBarMusic1"
app:tint="#color/remain_black" />
<ImageView
android:visibility="gone"
android:id="#+id/btPauseMusic"
android:layout_width="80dp"
android:layout_height="80dp"
android:alpha="0.9"
android:background="#drawable/pause"
android:theme="#style/Button.White"
app:layout_constraintEnd_toStartOf="#+id/text_time"
app:layout_constraintStart_toEndOf="#+id/playerPositionMusic"
app:layout_constraintTop_toBottomOf="#+id/seekBarMusic1"
app:tint="#color/remain_black" />
The code I used before:
public class m1 extends AppCompatActivity {
ImageView btPlay, btPause;
TextView playerPosition, playerDuration;
CircularSeekBar seekBar;
MediaPlayer mediaPlayer;
Handler handler = new Handler();
Runnable runnable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_m1);
findViewById(R.id.backm1).setOnClickListener(v -> onBackPressed());
playerPosition = findViewById(R.id.playerPosition);
playerDuration = findViewById(R.id.playerDuration);
replay = findViewById(R.id.replay);
forward = findViewById(R.id.forward);
seekBar = findViewById(R.id.seekBar);
btPause = findViewById(R.id.btPause);
btPlay = findViewById(R.id.btPlay);
mediaPlayer = MediaPlayer.create(this,R.raw.ding_dong);
runnable = new Runnable() {
#Override
public void run() {
seekBar.setProgress(mediaPlayer.getCurrentPosition());
handler.postDelayed(this,500);
}
};
int duration = mediaPlayer.getDuration();
String sDuration = convertFormat(duration);
playerDuration.setText(sDuration);
btPlay.setOnClickListener(v -> {
mediaPlayer.start();
btPlay.setVisibility(View.GONE);
btPause.setVisibility(View.VISIBLE);
seekBar.setMax(mediaPlayer.getDuration());
handler.postDelayed(runnable, 0)
});
btPause.setOnClickListener(v -> {
mediaPlayer.stop();
btPause.setVisibility(View.GONE);
btPlay.setVisibility(View.VISIBLE);
handler.removeCallbacks(runnable);
});
seekBar.setOnSeekBarChangeListener(new CircularSeekBar.OnCircularSeekBarChangeListener() {
#Override
public void onProgressChanged(CircularSeekBar circularSeekBar, float v, boolean b) {
if (b) {
mediaPlayer.seekTo((int) v);
}
playerPosition.setText(convertFormat(mediaPlayer.getCurrentPosition()));
}
#Override
public void onStopTrackingTouch(CircularSeekBar circularSeekBar) {
}
#Override
public void onStartTrackingTouch(CircularSeekBar circularSeekBar) {
}
});
mediaPlayer.setOnCompletionListener(mp -> {
btPause.setVisibility((View.GONE));
btPlay.setVisibility(View.VISIBLE);
mediaPlayer.seekTo(0);
});
}
}
#SuppressLint("DefaultLocale")
private String convertFormat(int duration) {
return String.format("%02d:%02d"
,TimeUnit.MILLISECONDS.toMinutes(duration)
,TimeUnit.MILLISECONDS.toSeconds(duration) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)));
}
#Override
public void onPause()
{
if(mediaPlayer != null && mediaPlayer.isPlaying())
{
mediaPlayer.stop();
}
super.onPause();
}
}
The code I am using now:
btPause = findViewById(R.id.btPauseMusic);
btPlay = findViewById(R.id.btPlayMusic);
btPlay.setOnClickListener(v -> {
startService(new Intent(this, BackgroundMusicService.class));
btPlay.setVisibility(View.GONE);
btPause.setVisibility(View.VISIBLE);
});
btPause.setOnClickListener(v -> {
stopService(new Intent(this, BackgroundMusicService.class));
btPause.setVisibility(View.GONE);
btPlay.setVisibility(View.VISIBLE);
});
}
And the service class:
public class BackgroundMusicService extends Service {
private MediaPlayer mediaPlayer;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
mediaPlayer = MediaPlayer.create(this, R.raw.ding_dong);
mediaPlayer.start();
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
mediaPlayer.stop();
}
}
You are using different MediaPlayer in the Activity from the one in the backgroundService
you don't need to initialize another mediaPlayer in the activity you have to control the one in the backgroundService with Broadcast receivers like this for example to pause a the mediaPlayer in the backgroundService you do something like this
private BroadcastReceiver pausePlayingAudio = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
pause();
updateMetaData();
buildNotification(PlaybackStatus.PAUSED);
playbackStatus = PlaybackStatus.PAUSED ;
}
};
private void register_pausePlayingAudio(){
IntentFilter intentFilter = new IntentFilter(SongAdapter.ACTION_PAUSE) ;
registerReceiver(pausePlayingAudio , intentFilter) ;
}
and you call register_pausePlayingAudio() in onCreate() of the backGroundService
now say when a user clicks on a button in the activity and you want to pause you use something like this
sendBroadcast(new Intent(SongAdapter.ACTION_PAUSE));
I have and old project not finished doesn't have a seekBar but the seekBar implementation will be similar just instead of pause() you call seekTo() so you can check it
the repo
This article Creating Media player service shows step by step on how you can implement a service to create a Mediaplayer application to run in the background.

Why my onStop() and onStart() methods work not correctly together in my stopwatch Android app?

A stopwatch is started by click on a Start button.
I make the Activity invisible, by pushing the home button on a device.
But stopwatch timing doesn’t stop according to the onStop() method: since when the activity is visible again, the stopwatch counting looks as it has never been stopped (the numbers continue increasing in the non-focus state despite the onStop() method).
However, if I deleted the onStart() method, the timing stops correctly, according to the onStop(), after pushing the home device button.
The stopwatch, by itself, counts correctly, timing is good.
There are only visible – invisible, stop-start timing problems, onStop() - onStart() methods interaction.
I tried combination onPause() - onResume(), include onRestart() and
so on, but the result is the same.
What’s wrong with my code?
I would be much appreciated for helping
package com.example.stopwatch;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.TextView;
import java.lang.System;
import java.util.Locale;
public class StopwatchActivity extends Activity {
//Number of seconds in stopwatch.
private int milliseconds = 0;
//Indicates whether a stopwatch is running.
private boolean running;
// Presents time in millis, when the click on Start button is executed.
private int startMillis;
// Shows whether the stopwatch was running when activity became invisible.
private boolean wasRunning;
#Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_stopwatch);
if(saveInstanceState != null) {
milliseconds = saveInstanceState.getInt("milliseconds");
running = saveInstanceState.getBoolean("running");
wasRunning = saveInstanceState.getBoolean("wasRunning");
startMillis = saveInstanceState.getInt("startMillis");
}
runTimer();
}
#Override
public void onSaveInstanceState(Bundle saveInstanceState) {
saveInstanceState.putInt("milliseconds", milliseconds);
saveInstanceState.putBoolean("running", running);
saveInstanceState.putBoolean("wasRunning", wasRunning);
saveInstanceState.putInt("startMillis", startMillis);
}
#Override
protected void onStop() {
super.onStop();
wasRunning = running;
running = false;
}
#Override
protected void onStart() {
super.onStart();
if (wasRunning) {
running = true;
}
}
//Run the stopwatch on a Start button click.
public void onClickStart(View view) {
running = true;
startMillis = (int)System.currentTimeMillis();
}
//Stop the stopwatch on a Stop button click.
public void onCLickStop(View view) {
running = false;
}
//Reset the stopwatch on a Reset button click.
public void onClickReset(View view) {
running = false; milliseconds = 0;
}
private void runTimer() {
final TextView timeView = (TextView)findViewById(R.id.time_view);
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
int minutes = (int)((milliseconds%3600000)/60000);
int secs = (int)((milliseconds%60000)/1000);
int msecs = milliseconds%1000;
String time = String.format(Locale.getDefault(),
"%02d:%02d:%03d", minutes, secs, msecs);
timeView.setText(time);
if (running) {
milliseconds = (int)(System.currentTimeMillis()-startMillis);
}
handler.postDelayed(this, 1);
}
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="com.example.stopwatch.StopwatchActivity">
<TextView
android:id="#+id/time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textAppearance="#android:style/TextAppearance.Large"
android:textSize="56sp" />
<Button
android:id="#+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:onClick="onClickStart"
android:text="#string/start" />
<Button
android:id="#+id/stop_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:onClick="onCLickStop"
android:text="#string/stop" />
<Button
android:id="#+id/reset_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:onClick="onClickReset"
android:text="#string/reset" />
</LinearLayout>
There are two problems at least:
Let's consider that you evaluate this expression twice, so that the second evaluation would be 5 seconds after the first evaluation in the timeline:
milliseconds = (int)(System.currentTimeMillis()-startMillis);
Thus, milliseconds value always increases as time goes forward! It means even if a user presses the start button, waits for 2 seconds, presses the stop button, waits for 3 seconds, presses the start button, milliseconds value will be equaled to 5 as the stopwatch starts ticking, not to 2 as you expect!
You should know that code inside Runnable in your codebase doesn't stop while a user works with another application. So you should somehow prevent this code from executing because it's a bad user experience when the code is executing while it shouldn't.
Thank you, Николай Гольцев, for advise!
I solved the problem:
I changed expression which evaluates milliseconds in a method run(), by adding new variable (saveMillis). There are some changes in «click methods» too. The code below works well now
package com.example.stopwatch;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.TextView;
import java.lang.System;
import java.util.Locale;
public class StopwatchActivity extends Activity {
//Number of seconds in stopwatch.
private int milliseconds = 0;
//Indicates whether a stopwatch is running.
private boolean running;
// Presents time in millis, when the click on Start button is executed.
private int startMillis;
// Shows whether the stopwatch was running when activity became invisible.
private boolean wasRunning;
//Save the current time in milliseconds, when the timing is stopped.
private int saveMillis = 0;
#Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_stopwatch);
if (saveInstanceState != null) {
milliseconds = saveInstanceState.getInt("milliseconds");
running = saveInstanceState.getBoolean("running");
wasRunning = saveInstanceState.getBoolean("wasRunning");
startMillis = saveInstanceState.getInt("startMillis");
saveMillis = saveInstanceState.getInt("saveMillis");
}
runTimer();
}
#Override
public void onSaveInstanceState(Bundle saveInstanceState) {
saveInstanceState.putInt("milliseconds", milliseconds);
saveInstanceState.putBoolean("running", running);
saveInstanceState.putBoolean("wasRunning", wasRunning);
saveInstanceState.putInt("startMillis", startMillis);
saveInstanceState.putInt("saveMillis", saveMillis);
}
#Override
protected void onStop() {
super.onStop();
wasRunning = running;
running = false;
saveMillis = milliseconds;
}
#Override
protected void onStart() {
super.onStart();
if (wasRunning) {
running = true;
startMillis = (int) System.currentTimeMillis();
}
}
//Run the stopwatch on a Start button click.
public void onClickStart(View view) {
running = true;
startMillis = (int) System.currentTimeMillis();
}
//Stop the stopwatch on a Stop button click.
public void onCLickStop(View view) {
running = false;
saveMillis = milliseconds;
}
//Reset the stopwatch on a Reset button click.
public void onClickReset(View view) {
running = false;
milliseconds = 0;
saveMillis = 0;
}
//Resume timing.
public void onClickResume(View view) {
running = true;
startMillis = (int) System.currentTimeMillis();
}
private void runTimer() {
final TextView timeView = (TextView) findViewById(R.id.time_view);
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
int minutes = (int) ((milliseconds % 3600000) / 60000);
int secs = (int) ((milliseconds % 60000) / 1000);
int msecs = milliseconds % 1000;
String time = String.format(Locale.getDefault(),
"%02d:%02d:%03d", minutes, secs, msecs);
timeView.setText(time);
if (running) {
milliseconds = saveMillis + ((int) (System.currentTimeMillis() - startMillis));
}
handler.postDelayed(this, 1);
}
});
}
}

My countdown timer code not working properly

I am creating a simple countdown timer in android studio which has a start and a pause button. The timer countdown starts but does not pause instead it starts increasing and decreasing by clicking pause button. Here is my code.
<TextView
android:id="#+id/txtview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Counter"
android:textSize="75sp"
android:textColor="#ff0000"
/>
<Button
android:id="#+id/push_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Count Down"
android:onClick="perform_action"
android:textSize="35sp"
/>
<Button
android:id="#+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pause Count Down"
android:onClick="perform_action"
android:textSize="35sp"
/>
Java code
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void perform_action(View view)
{
long totalMilliseconds = 90000;
long interval = 1;
Button b1 = (Button)findViewById(R.id.push_button);
String push = b1.getText().toString();
CountDownTimer timer;
timer = new CountDownTimer(40000, 1000) {
TextView t1 = (TextView)findViewById(R.id.txtview1);
#Override
public void onTick(long l) {
t1.setText(""+(l));
}
#Override
public void onFinish() {
t1.setText("Finish");
}
};
if(push.equals("Start Count Down")){
timer.start();
}
else if(push.equals("Pause Count Down")){
timer.cancel();
}
}
}
Please provide me some help
It looks like you're spawning a new timer every time you click the button, from the snippet you posted. You can just use an OnClickListener.
You can access it like so (example uses a boolean to switch states);
Boolean ButtonClicked = false;
b1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
//Perform your click logic to start and stop the timer
if (!ButtonClicked)) {
ButtonClicked = true;
timer.start();
} else {
ButtonClicked = false;
timer.cancel();
}
}
});

Issue with Start Button on Android Calculator

I am working on an Android calculator app not using any of the built in timer based classes in Android, but instead using Handlers and Threads to update the UI. I'm not sure if there is a problem with my logic or not, but for whatever reason when I set a time and hit the Start button, nothing happens on the screen at all. The targeted TextView does not decrease as it should. Again, I may have made a simple errors (or a few), but I am posting my java and xml files for you all to look at. Thanks in advance for any responses.
TimerActivity.java
package com.example.stins.intentsandtimer;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.NumberPicker;
import android.widget.TextView;
import android.os.Handler;
import android.os.Message;
import android.content.Context;
import android.os.Vibrator;
public class TimerActivity extends AppCompatActivity implements View.OnClickListener {
TextView hours, minutes, seconds;
Button numberPicker;
private int hrs, min, sec;
private boolean start;
Handler timerHandler = new Handler(){
/**
* Handler for the timer class. It receives the onStart runnable to allow the textviews
* to be updated. It checks to see if all textviews are empty and only updates them if
* they follow the conditions of a traditional timer. Including moving from 1 hour to 59 minutes.
* The handler also sends the Vibrator function once the timer is complete.
* #param msg
*/
#Override
public void handleMessage(Message msg){
super.handleMessage(msg);
TextView txtSeconds = (TextView) findViewById(R.id.textview_seconds);
TextView txtMinutes = (TextView) findViewById(R.id.textview_minutes);
TextView txtHours = (TextView) findViewById(R.id.textview_hours);
int zeroCheck = Integer.parseInt(txtSeconds.getText().toString());
if (zeroCheck > 0) {
sec -= 1;
txtSeconds.setText(sec + "");
} else if (min > 0 && sec == 0) {
min -= 1;
txtMinutes.setText(min + "");
sec = 59;
txtSeconds.setText(sec + "");
} else if (hrs > 0 && min == 0 && sec == 0) {
hrs -= 1;
txtHours.setText(hrs + "");
min = 59;
txtMinutes.setText(min + "");
sec = 59;
txtSeconds.setText(sec + "");
} else {
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(1000);
}
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer);
this.setTitle("Timer");
Button btnStart = (Button) findViewById(R.id.start_button);
Button btnStop = (Button) findViewById(R.id.stop_button);
Button btnReset = (Button) findViewById(R.id.reset_button);
hours = (TextView) findViewById(R.id.textview_hours);
numberPicker = (Button) findViewById(R.id.btn_set_hours);
numberPicker.setOnClickListener(this);
minutes = (TextView) findViewById(R.id.textview_minutes);
numberPicker = (Button) findViewById(R.id.btn_set_minutes);
numberPicker.setOnClickListener(this);
seconds = (TextView) findViewById(R.id.textview_seconds);
numberPicker = (Button) findViewById(R.id.btn_set_seconds);
numberPicker.setOnClickListener(this);
btnReset.setOnClickListener(new Button.OnClickListener() {
public void onClick(View view) {
TextView txtSeconds = (TextView) findViewById(R.id.textview_seconds);
TextView txtMinutes = (TextView) findViewById(R.id.textview_minutes);
TextView txtHours = (TextView) findViewById(R.id.textview_hours);
sec = 0;
min = 0;
hrs = 0;
txtSeconds.setText(sec+"");
txtMinutes.setText(min+"");
txtHours.setText(hrs+"");
}
}
);
btnStart.setOnClickListener(new Button.OnClickListener() {
public void onClick(View view) {
start = true;
onStart();
}
}
);
btnStop.setOnClickListener(new Button.OnClickListener() {
public void onClick(View view) {
start = false;
}
}
);
}
protected void onStart(){
super.onStart();
final Thread myThread = new Thread(new Runnable(){
#Override
public void run() {
while (sec > 0 || min > 0 || hrs > 0) {
if(start) {
try {
Thread.sleep(1000);
timerHandler.sendMessage(timerHandler.obtainMessage());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
}
}
}
});
myThread.start();
}
public void onClick (View v){
switch (v.getId()) {
case R.id.btn_set_hours:
hourPickerDialog();
break;
case R.id.btn_set_minutes:
minutePickerDialog();
break;
case R.id.btn_set_seconds:
secondPickerDialog();
break;
default:
break;
}
}
private void hourPickerDialog(){
NumberPicker myNumberPicker = new NumberPicker(this);
myNumberPicker.setMaxValue(99);
myNumberPicker.setMinValue(0);
NumberPicker.OnValueChangeListener myValChangedListener = new NumberPicker.OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
hours.setText(""+newVal);
}
};
myNumberPicker.setOnValueChangedListener(myValChangedListener);
AlertDialog.Builder builder = new AlertDialog.Builder(this).setView(myNumberPicker);
builder.setTitle("Set Hours");
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
}
private void minutePickerDialog(){
NumberPicker myNumberPicker = new NumberPicker(this);
myNumberPicker.setMaxValue(59);
myNumberPicker.setMinValue(0);
NumberPicker.OnValueChangeListener myValChangedListener = new NumberPicker.OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
minutes.setText(""+newVal);
}
};
myNumberPicker.setOnValueChangedListener(myValChangedListener);
AlertDialog.Builder builder = new AlertDialog.Builder(this).setView(myNumberPicker);
builder.setTitle("Set Minutes");
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
}
private void secondPickerDialog(){
NumberPicker myNumberPicker = new NumberPicker(this);
myNumberPicker.setMaxValue(59);
myNumberPicker.setMinValue(0);
NumberPicker.OnValueChangeListener myValChangedListener = new NumberPicker.OnValueChangeListener() {
#Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
seconds.setText(""+newVal);
}
};
myNumberPicker.setOnValueChangedListener(myValChangedListener);
AlertDialog.Builder builder = new AlertDialog.Builder(this).setView(myNumberPicker);
builder.setTitle("Set Seconds");
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
}
}
activity_timer.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:orientation="horizontal"
android:gravity="center">
<TextView
android:id="#+id/textview_hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="00"
android:textSize="70sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text=":"
android:textSize="70sp"/>
<TextView
android:id="#+id/textview_minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="00"
android:textSize="70sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text=":"
android:textSize="70sp"/>
<TextView
android:id="#+id/textview_seconds"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="00"
android:textSize="70sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Button
android:id="#+id/btn_set_hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hours"/>
<Button
android:id="#+id/btn_set_minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Minutes"/>
<Button
android:id="#+id/btn_set_seconds"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Seconds"/>
</LinearLayout>
<Button
android:id="#+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:background="?selectableItemBackgroundBorderless"
android:text="#string/timer_start"
style="#style/MyButton"
/>
<Button
android:id="#+id/stop_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:background="?selectableItemBackgroundBorderless"
android:text="#string/timer_stop"
style="#style/MyButton"/>
<Button
android:id="#+id/reset_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="?selectableItemBackgroundBorderless"
android:text="#string/timer_reset"
style="#style/MyButton"/>
</LinearLayout>
There's several things going on in your code. I won't try to address them all but just some to get your code doing about what it should. I've copied & tried your code & it actually changes the display for me. I skipped your time picker dialogs & just set sec=20 to start. If you're not getting any changing display, is the display being set initially from the time pickers?
Anyway, 1st let's talk about debugging. One way to do this is to put Log statements in your code. Start by putting this at the top of the file
private final static String TAG = "TimerActivity";
Then in the code have things like this:
// put this in the start button click listener
Log.d(TAG, "Start clicked");
// or this in handleMessage
Log.d(TAG, "handleMessage(), seconds = " + sec);
Having these Log message can help you know what your program has done & what it hasn't, plus show you some variable values. You could also use the debugger which I won't get into now.
Now for your code. onStart() is a lifecycle method. You should not call it yourself. Rename your method (maybe something like onStartButton()). As you have it now, you have 2 instances of your thread running and your counter goes down twice in each second.
In handleMessage(), you have variables (hrs, min, sec) that you use to track the time but you also have zeroCheck that you read from the text on the display. The better thing to do would be use the variables you're already keeping anyway (if(sec > 0) { sec -= 1;...). I didn't verify your logic in the rest of these conditions. Once the display is updating, I'll leave that for you.
Lastly, txtSeconds.setText(sec + ""); is not a good way to use setText() (it's probably OK for Log messages but it's better to get accustomed to using text in other ways). There is more than 1 good way to display text but for this instance, you need special formatting. That is you want your display to show a leading 0 for each number "00:09:07" not "0:9:7". You can get that with
txtSeconds.setText(String.format("%02d", sec));
This way always gives a 2 digit display, from 0 to 59. Other useful formatters are "%08x" for 32 bit hexadecimal or "%.2f" which limits display to 2 places past the decimal place like for showing dollars and cents.
So, none of these will fix the problem in your post but they will get your final code closer to what it needs to be. As I said, your code updates the display as it is for me (not using the time pickers). You can start by setting sec to a fixed number then hit the "Start" button to see what happens. If there are problems in your time pickers, you can use Log messages to track down the bugs & fix them.
EDIT:
So what's happening with your timer not starting is that, while you change the display in your number picker, you don't set the underlying variables (sec etc.) Define some variables to use as temp storage (temp_sec etc.) then set this in onValueChange(),
temp_sec = newVal;
Now in your positiveButton onClick(), you'll have
sec = temp_sec;

Creating a Singleton CountDownTimer in Android

I'm a beginner in android and I've written an activity. It contains a CountDownTimer that counts down from a particular value. It also contains a Button that loads text information and a textview to display count.
Below is the code for Activity1:
public class Screen extends Activity1 implements OnClickListener {
private static final int MILLIS_PER_SECOND = 1000;
private static final int SECONDS_TO_COUNTDOWN = 1;
TextView Time;
int totaltime;
Button startTimer, howTo, pause;
protected CountDownTimer MyTimer;
int PracticeCount;
long tot;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.pushupscreen);
getRefs();
getSpeed();
getCount();
setTotalTime();
startTimer.setOnClickListener(this);
pause.setOnClickListener(this);
}
private void getRefs() {
// Initialize layout resources
Time = (TextView) findViewById(R.id.tvTime);
startTimer = (Button) findViewById(R.id.bStart);
howTo = (Button) findViewById(R.id.btHowTo);
pause = (Button) findViewById(R.id.bPause);
howTo.setOnClickListener(this);
}
private void getTheCount() {
//get count from SharedPreferences
}
private void getSpeed() {
//get speed from SharedPreferences
}
private void setCount(){
totalTime=speed*count;}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (v == startTimer) {
try {
showTimer(time);
} catch (NumberFormatException e) {
// method ignores invalid (non-integer) input and waits
// for something it cant use
}
} else if (v == pause) {
MyTimer.cancel();
Timer.setText("Resume");
} else if (v == howTo) {
//Launch screen containing information
}
}
private void showTimer(long time) {
if (MyTimer != null) {
MyTimer.cancel();
}
MyTimer = new CountDownTimer(tot2, MILLIS_PER_SECOND) {
#Override
public void onTick(long millisUntilFinished) {
tot = millisUntilFinished;
long seconds = millisUntilFinished / 1000;
Time.setText(String.format("%02d", seconds / 60) + ":"
+ String.format("%02d", seconds % 60));
}
#Override
public void onFinish() {
Time.setText("KABOOM!");
}
}.start();
}
}
And here is the layout file for this:
<TextView
android:id="#+id/tvTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:gravity="center"
android:padding="10dip"
android:text="#string/starttime"
android:textSize="60sp" />
<Button
android:id="#+id/bStart"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_above="#+id/tvTime"
android:text="Start" />
<Button
android:id="#+id/bPause"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_above="#+id/tvTime"
android:layout_toRightOf="#+id/btHowTo"
android:text="Pause" />
<TextView
android:id="#+id/tvCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/btHowTo"
android:layout_centerHorizontal="true"
android:layout_marginTop="39dp"
android:text="25"
android:textSize="80sp"
android:textAlignment="center"/>
My questions:
1.How do I create 4 activities that use the same layout and the same timer? Each Activity loads a different content in the textview and a different screen on the click of HowTo button.
2.How can Activity1 be designed to run for 1/4th the time set and pass remaining time to Activity2? Is it possible?
I would really appreciate any help and advice that you can provide. Thanks.
A couple things here.
Its very easy to re-use layouts. In each activity's onCreate you would just call:
setContentView(R.layout.pushupscreen); The pushupscreen.xml file can be shared across all activities this way.
What you probably want to do is persist a timestamp to some common data source for all the activities. This could be a write to a SharedPreferences file: Documentation here. Then as each activity resumes, check how much time has already passed by comparing this timestamp to the current timestamp. You could also pass the timestamp as an extra in the intent to start up the subsequent activities. The documentation for that can be found here and here
You could make a custom control, which is basically a new class which inherits some other control's class (for example a LinearLayout or a RelativeLayout). You could then load a view's XML to your new layout or programmatically create new controls inside your control. More info here:
Custom components in Android
After a 1/4 of your countdown period, you can create and send an Intent to start a new activity in the onTick method. You can also put the remaining 3/4 as a millisecond value (of type long) in an intent extra. You can then obtain this value in the new activity and invoke a custom CountDownTimer child there for the rest of your countdown. Then you can finally execute what you wish after the countdown is done in the onFinish() method.

Categories