What I've built is a timer with multiple timer styles, using a spinner so the user can switch quickly between the timer they want to use.
My problem is with the switch statement - each timer works fine on its own but when going (example) from the Basic Timer to the Countdown Timer via the spinner, the first timer (Basic Timer) continues being run alongside the newly selected timer. In this example, the bug displays as 1 second down 1 second up 1 second down 1 second up etc...
My questions is: Is there a command that can be used to "kill" a function that I no longer want to continue running? If not, is there a better way I could have organized the code so the switch statements will keep each timer process separate? I have tried a Boolean check with no luck and breaking up the timer running-specific code differently has not worked for me so far.
Please see the relevant code below:
package com.mtag.app.muaythaiathletesguide;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import java.util.Locale;
public class TimerActivity extends Activity implements AdapterView.OnItemSelectedListener {
private int seconds = 0; // Number of seconds passed
private boolean running; // Check whether timer is running
private boolean wasRunning;
private int timeCap = 0; // Custom max time, stop timer when reached and reset here for countdown
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer);
// Timer Selection Spinner
Spinner spinner = (Spinner) findViewById(R.id.timer_spinner);
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.timer_spinner, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// Apply the adapter to the spinner
spinner.setAdapter(adapter);
// Spinner click listener
spinner.setOnItemSelectedListener(this);
// Restore activity's state by getting values from Bundle
if (savedInstanceState != null && running) {
seconds = savedInstanceState.getInt("seconds");
running = savedInstanceState.getBoolean("running");
wasRunning = savedInstanceState.getBoolean("wasRunning");
}
}
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id){
String selection = parent.getItemAtPosition(pos).toString();
// TODO: Remove Toast outputs after testing
// Call Timer types when corresponding position is chosen
switch(pos) {
case 0: // Basic Stopwatch: Count from 0:00:00 to 99:59:59 (or cap)
onDestroy();
running = false; // Stop clock
seconds = 0; // Reset seconds to zero
timeCap = seconds; // Set time cap to match seconds on the clock, for reset point
runBasicTimer();
break;
case 1: // Countdown: Count from 99:59:59 (or cap) to 0:00:00
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
onDestroy();
running = false;
seconds = 1200; // Default cap 20:00:00
timeCap = seconds;
runCountdownTimer();
break;
case 2: // Tabata: Beep every 20th and 30th second. Reset to 0:00:00 on each 30th second
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
running = false;
seconds = 0;
runTabataTimer();
break;
case 3: // Fight Gone Bad: 17min cap, beep on each minute
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
running = false;
seconds = 0;
runFGBTimer();
break;
case 4: // "3 On 1 Off": Beep every 3rd and 4th minute
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
running = false;
seconds = 0;
runThreeOneTimer();
break;
case 5: // "5 On 1 Off": Beep every 5th and 6th minute
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
running = false;
seconds = 0;
runFiveOneTimer();
break;
default:
running = false;
seconds = 0;
Toast.makeText(parent.getContext(), "Error", Toast.LENGTH_LONG).show();
break;
}
}
public void onNothingSelected(AdapterView<?> parent){
// Another interface callback
}
#Override
// Save the state of variables
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("seconds", seconds);
savedInstanceState.putBoolean("running", running);
savedInstanceState.putBoolean("wasRunning", wasRunning);
}
#Override
protected void onResume() {
super.onResume();
// If the stopwatch was running at stop, set it running again
if (wasRunning)
running = true;
}
#Override
protected void onPause() {
super.onPause();
// Record state of stopwatch, running or not running
wasRunning = running;
running = false;
}
public void onClickStart(View view) {
running = true; // Start stopwatch
}
public void onClickStop(View view) {
running = false; // Stop stopwatch
}
public void onClickReset(View view) {
seconds = timeCap; // Reset seconds to zero
}
private void runBasicTimer() {
final TextView timeView = (TextView)findViewById(R.id.time_view);
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
// Format time to hours, minutes, and seconds
String time = String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, secs);
timeView.setText(time);
if (running) {
seconds++;
}
// Don't allow timer to go over 99:59:59
if (seconds >= 359999) {
running = false;
Toast.makeText(getApplicationContext(), "Maximum time reached", Toast.LENGTH_LONG).show();
}
// Post code again with delay of one second
handler.postDelayed(this, 1000);
}
});
}
private void runCountdownTimer() {
final TextView timeView = (TextView)findViewById(R.id.time_view);
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
// Format time to hours, minutes, and seconds
String time = String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, secs);
timeView.setText(time);
if (running) {
seconds--;
}
// Don't allow timer to go under 0:00:00
if (seconds <= 1) {
running = false;
Toast.makeText(getApplicationContext(), "Maximum time reached", Toast.LENGTH_LONG).show();
}
// Post code again with delay of one second
handler.postDelayed(this, 1000);
}
});
}
private void runTabataTimer() {
}
private void runFGBTimer() {
}
private void runThreeOneTimer() {
}
private void runFiveOneTimer() {
}
}
And here's the .xml file:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
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"
android:background="#color/colorWhite"
tools:context="com.mtag.app.muaythaiathletesguide.TimerActivity">
<TextView
android:id="#+id/timer_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="#string/timer_style"/>
<Spinner
android:id="#+id/timer_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="20dp"
android:background="#color/colorLightGrey"
android:minHeight="40dp" />
<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="90sp" />
<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:gravity="center_horizontal"
android:orientation="horizontal"
android:padding="16dp">
<Button
android:id="#+id/start_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="#color/colorTimerGreen"
android:onClick="onClickStart"
android:text="#string/start" />
<Button
android:id="#+id/stop_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="8dp"
android:background="#color/colorTimerRed"
android:onClick="onClickStop"
android:text="#string/stop" />
<Button
android:id="#+id/reset_button"
android:layout_width="90dp"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:onClick="onClickReset"
android:text="#string/reset" />
</LinearLayout>
</GridLayout>
Your timer/countdown is running on another thread. That is okay by itself but comes with provisions that you have missed. For example, you are trying to set running and seconds when a new item is selected. Those two variables exist in the context of your UI thread. When they are used in the Runnable they are simply put, copied as final variables and used by the new thread. This happens when you reference a variable inside an anonymous class. This causes the Runnable to not see the variables you are changing because they are not the same anymore.
Now how to fix this?
Instead of passing an anonymous Runnable, you should hold a reference to it. Right now, you are creating a timer and just letting it go. That makes it very hard to control it as you can see.
Runnable activeTimer;
private void runBasicTimer() {
//... first remove any other activeTimer if exists and make sure it is cancelled properly (see below)
activeTimer = new Runnable() {//Your code}
handler.post(activeTimer);
}
This way you can always remove the activeTimer from the Handler like this.
This is still not complete. Because you also repost the runnable regularly. We also want to cancel that as well so that it is not reposted to the handler. To do this you should extend the Runnable class so that you can change running in it.
public class TimerRunnable extends Runnable {
public boolean mRunning = true;
#Override
public void run() {
//your code
if (mRunning) {
handler.postDelayed(this, 1000);
}
}
}
Now when you want to create a new timer:
activeTimer = new TimerRunnable(); //instead of just new Runnable()
Perfect! Now you can easily cancel it because you have access to its thread and you have access to the thread's mRunning variable. In your onItemSelected instead of just changing running you can do this:
if (activeTimer != null) {
activeTimer.mRunning = false;
}
How can i optimize my stopwatch in my app to use less cpu?
I am running a stopwatch using an asynctask class in the mainActivity. The doInBackground() method increments the values for the hours, minutes, seconds and centiseconds(10th of a second). The onProgressUpdate() method is responsible for updating 4 imageViews that display the hrs, mins, sec, centisec.
The problem i have is that the stopwatch uses about on average 50%+ cpu usage according to android studio(50% user and 30% kernel usage) and a cpu monitoring app that i installed on the device (2013 HTC one m7). The default android operating system stopwatch uses only about 10% cpu usage. If i use textViews instead of image views the cpu usage drop to half (less than 25%). But it is still more than 10% and i also i want to keep the style of digits im using.
Would caching the images help in anyway? source
I have also considered using XML drawables for the digits instead of bitmaps, but i don't know how effective this will be or if its even possible to create xml drawables of the digits
Lend me your knowledge stackoverflow
main XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#763768"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="com.example.aroboius.stopwatch.MainActivity"
tools:showIn="#layout/activity_main">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/hoursImage"
android:layout_marginRight="20dp"
android:src="#drawable/digit00" />
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/minutesImage"
android:layout_marginRight="20dp"
android:src="#drawable/digit00" />
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/secondsImage"
android:layout_marginRight="20dp"
android:src="#drawable/digit00" />
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/centiSecondsImage"
android:src="#drawable/digit00" />
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
ImageView hoursIMG, minutesIMG, secondsIMG, centiSecondsIMG;
TextView hoursText, minutesText, secondsText, centicsecondsText;
int centiseconds, seconds, minutes, hours ;
long startMS , endMS , elapsed ;
boolean timerRunning;
String [] digit = {"digit00","digit01","digit02","digit03","digit04","digit05","digit06","digit07","digit08","digit09", "digit10", "digit11","digit12","digit13","digit14","digit15","digit16","digit17","digit18","digit19","digit20", "digit21","digit22","digit23","digit24","digit25","digit26","digit27","digit28","digit29","digit30","digit31",
"digit32","digit33","digit34","digit35","digit36","digit37","digit38","digit39","digit40","digit41","digit42","digit43","digit44","digit45","digit46","digit47","digit48","digit49","digit50","digit51","digit52","digit53",
"digit54","digit55","digit56","digit57","digit58","digit59","digit60","digit61","digit62","digit63","digit64","digit65","digit66","digit67","digit68","digit69","digit70","digit71","digit72","digit73","digit74","digit75",
"digit76","digit77","digit78","digit79","digit80","digit81","digit82","digit83","digit84","digit85","digit86","digit87","digit88","digit89", "digit90","digit91","digit92","digit93","digit94","digit95","digit96","digit97","digit98","digit99"} ;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//initializing values
centiseconds = 0; seconds = 0; minutes = 0; hours = 0;
startMS = 0; endMS = 0; elapsed = 0;
hoursIMG = (ImageView) findViewById(R.id.hoursImage);
minutesIMG = (ImageView) findViewById(R.id.minutesImage);
secondsIMG = (ImageView) findViewById(R.id.secondsImage);
centiSecondsIMG = (ImageView) findViewById(R.id.centiSecondsImage);
//start asynctask/stopwatch
timerRunning = true; new asyncTask().execute();
}
class asyncTask extends AsyncTask<Void, Void, Void> {
//initialize a variable to the current system time
#Override
protected void onPreExecute() {
startMS = System.currentTimeMillis();
}
#Override
protected Void doInBackground(Void... params) {
//timerRunning a varible to stop/start the timer
while (timerRunning) {
//initialize a 2nd variable to the current system time
endMS = System.currentTimeMillis();
//get the difference between the 2 time variables
elapsed = endMS - startMS;
//once it is greater than or equal to 100ms increment the centis, mins, secs, hrs
if (elapsed >= 100) {
//reset the starting variable to repeat the process. it also compensating if elapses is greater than 100ms
startMS = endMS - (elapsed - 100);
centiseconds++;
if (centiseconds > 9) {
centiseconds = 0;
seconds++;
if (seconds > 59) {
seconds = 0;
minutes++;
if (minutes > 59) {
minutes = 0;
hours++;
}
}
}
//call method to update the images
publishProgress();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... values) {
//get resource IDs for images that represent the values of hrs, mins, secs using the string array created earlier
int hourResID = getResources().getIdentifier(digit[hours], "drawable", getPackageName());
int minResID= getResources().getIdentifier(digit[minutes], "drawable", getPackageName());
int secResID= getResources().getIdentifier(digit [seconds], "drawable", getPackageName());
int csecResID= getResources().getIdentifier(digit[centiseconds], "drawable", getPackageName());
//set images of imageViews
centiSecondsIMG.setImageResource(csecResID);
secondsIMG.setImageResource(secResID);
minutesIMG.setImageResource(minResID);
hoursIMG.setImageResource(hourResID);
}
}
}
1:
Instead of burning your CPU and battery with a loop, you should use a CountDownTimer
new CountDownTimer(30000, 1000) { // 30sec, tick each second
public void onTick(long millisUntilFinished) {
publishProgress();
// mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
}
public void onFinish() {
mTextField.setText("done!");
}
}.start();
Or you can use a Runnable that you delay every time by the desired amount of time, let's say 200ms:
final static long REFRESH_RATE = 200L;
Handler mHandler = new Handler();
private final Runnable mRunnable = new Runnable() {
#Override
public void run() {
if (mStarted) {
long seconds = (System.currentTimeMillis() - t) / 1000;
statusBar.setText(String.format("%02d:%02d", seconds / 60, seconds % 60));
// cancel previous messages if they exist
handler.removeCallbacks(mRunnable);
handler.postDelayed(runnable, REFRESH_RATE);
}
}
};
start it:
mHandler.postDealyed(runnable, 0);
You can use a Timer with fixed rate:
new Timer().scheduleAtFixedRate(new TimerTask(){
#Override
public void run(){
publishProgress();
}
},0,1000);
Or you can use a ScheduledExecutorService, that will fix most of the problems you mentioned. See here and here.
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(1);
long lastSecondDisplayed = 0;
ScheduledFuture scheduledFuture =
scheduledExecutorService.schedule(new Callable() {
public Object call() throws Exception {
long now = System.currentTimeMillis() / 1000;
// add this optimisation, so you don't calculate and
// for sure don't refresh your UI (even slower)
// if it's not needed:
if (lastSecondDisplayed != now) {
lastSecondDisplayed = now;
// calculate whatever you want
publishProgress();
}
return "Called!";
}
}, 1, TimeUnit.SECONDS);
Optimisations:
move the 4 getResources().getIdentifier(... lines out of onProgressUpdate and prepare the 10 digits only once in onCreate.
It is always good to reuse resources in java, because when you're creating and disposing them frequently, like here, you'll finish your memory quite fast and the GC will have to free some memory for you. Both creating the objects, and especially garbage-collecting them takes a fair amount of time. By creating them only once and reusing them you keep yourself far from all this trouble.
It seems it was a problem with continuously resetting the imageViews to different drawables. The getResources().getIdentifier() function calls also somewhat contributed to extra cpu usage and GarbageCleaner(GC) problems.
Instead of creating an image resource array I created a drawable array that I can continually reference. I created it in onCreate().
final Drawable[] drawable = {ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit00),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit01),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit02),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit03),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit04),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit05),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit06),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit07),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit08),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit09),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit10)}
Then I set the images on the imageViews using the Drawable
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
centiSecondsIMG.setImageDrawable(drawable[centiseconds]);
secondsIMG.setImageDrawable(drawable[seconds]);
minutesIMG.setImageDrawable(drawable[minutes]);
hoursIMG.setImageDrawable(drawable[hours]);
}
});
Memory and cpu are now all perfectly fine and working normally.
i sitll dont know why changing the imageViews images rapidly using setImageResource() caused problems with cpu,memory and GC.
I'm trying to make a Video Player android application in which I have to display an ImageButton on the top of Video on a specific Time.
This is my layout
<FrameLayout
android:id="#+id/videoSurfaceContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<SurfaceView
android:id="#+id/videoSurface"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="#+id/thumbnail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:orientation="vertical"
android:visibility="gone" >
<ImageButton
android:id="#+id/imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/image" />
<TextView
android:id="#+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEST TEST"
android:textColor="#FFFFFF" />
</LinearLayout>
</FrameLayout>
and i have an ArrayList of type ThumbnailInfo where
public class ThumbnailInfo {
int time; //time in milliseconds
String body;
ThumbnailInfo(int time,String body){
this.time = time;
this.body = body;
}
}
So now I have time stored in the arraylist and i want to display the thumbnail 10 seconds before the specific time.
For e.g.
Suppose time stored in arraylist is 00:40 , 01:30 , 04:50 (just to make the question clear ,time is stored in milliseconds).
So when i'm playing the Video , I have to set the Visibility of thumbnail layout as VISIBLE at 00:30 , 01:20 , 04:40 and will set the visibility GONE at 00:41, 01:31 , 04:51
So my question is How can I check stored Time in ArrayList continuously and execute the above mentioned operation.
Currently what i have is the Current Position of the Video using mediaPlayer.getCurrentPosition();
Now I have to compare the current position of the video continuously with the time stored in ArrayList .
Is there any observer which can make the task easy or any other approach.
P.S. : User can pause the video multiple times.
Any help is welcome. Thanx in advance !!!
To simple you should convert time from minute to second start with 0. Such as:
VISIBLE at 00:30 , 01:20 , 04:40 => ArrayList< Visible > with values 30, 80, 280
INVISIBLE at 00:41, 01:31 , 04:51 => ArrayList< Invisible > with values 41, 61, 281
int seconds = 0;
CountDownTimer t = new CountDownTimer(Long.MAX_VALUE ,1000) { // interval 1s
// This is called every interval. (Every 1 seconds in this example)
public void onTick(long millisUntilFinished) {
checkVisible(ArrayList visible);// make visible button
checkInvisible(ArrayList invisible); // make invisible button
seconds++:
}
public void onFinish() {
System.out.println("finished");
seconds = 0;
}
}.start();
Remember call
t.cancel() when pause video
t.finish() when finish play video
reset seconds variable;
Try a timer:Example:
Timer wox=new Timer();
wox.scheduleAtFixedRate(new TimerTask() {
public void run() {
runOnUiThread(new Runnable() {
public void run() {
//your actions
}
});
}
}, 0, 1000);//timer time
Considered that the user may pause the video and restart it or maybe due to bad network, the video will not conform to the time you have assumed. So showing or hiding the imageButton should not depend on a timer.
You may consider using a runnable inside a handler, and it will execute every 1 second to check the progress of video displaying and show or hide imageButton accordingly.
something like the following code:
Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
int progress = checkVideoProgress();
determineShowOrHideImageButtonByProgress(progress);
if (videoIsEnd()){
handler.removeCallbacks(runnable);
}else {
handler.postDelayed(runnable,1000);//check every 1 second in this example.
}
}
};
In a word, the visibility of the imageButton should rely on the progress of video instead of the real time of os.
I am trying to use this timer...when I call countDownTimer.start()
I can see in the debugger that the timeleft variable get updated to the value=120000 that I want but the timer doesn't execute the onTick method. It jumps straight to onFinish. But if I give timeLeft a literal it works.
volatile long timeLeft=0;
CountDownTimer countDownTimer=new CountDownTimer( timeLeft,1000) {
#Override
public void onTick(long timeLeft) {
TextView timeView= (TextView)findViewById(R.id.timer);
long longTime=(timeLeft / 1000);
Integer intTime=(int)longTime;// convert long to int
timeView.setText(String.valueOf(intTime));}
#Override
public void onFinish() {correctDialog("Sorry,Time Is Up!!");}};}
(timeLeft is not fixed)
Here the first parameter in the CountDownTimer() constructor is the millisInFuture . You are giving this value as 0. So it will finish whenever you start the timer. Give its value how much time you want to execute the times like 10000 or 20000 etc...
So initialize timeleft
volatile long timeLeft=10000;
Check the documentation CountDownTimer
try like this:
public class MainActivity extends Activity {
TextView tv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
CountDownTimer timer = new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
int seconds = (int) (millisUntilFinished / 1000);
tv.setText("seconds remaining: " + String.valueOf(seconds));
}
public void onFinish() {
tv.setText("Finished!!");
}
}.start();
}
}
Xml:-
<RelativeLayout 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">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello_world" />
</RelativeLayout>
I think it's solve your problem.
You set the timeLeft = 0;this means that the timer would run for 0 milliseconds. the timeLeft should equal to the amount of milliseconds you want the timer to run for. So, for a thirty second timer, volatile long timeLeft = 30000;
Hope this helps
Precise Answer to the question is, you forgot to start the timer.
i.e. you missed countDownTimer.start();
Code for CountDownTimer:
int tickTime = 1000; //For every second
int finishTime = 30000; //Complete of countdowntimer
TextView yourTextView;
new CountDownTimer(tickTime, finishTime){
#Override
public void onTick(long l) {
Log.i("CountDownTimer", "onTick");
yourTextView.setText(String.valueOf(l));
}
#Override
public void onFinish() {
Log.i("CountDownTimer", "onFinish");
}
}.start();
This countdowntimer comes on every second in onTick() method and after 30 seconds it will come in onFinish() method.
Done
I have 2 virtually identical textviews in a layout that are updated by 2 similar bits of code. When the app runs (whether emulator or phone) one is updated but the other is not - but when run the same way in debug mode they are both updated. I've done lots of searching online, read lots of questions and answers and have tried various ways to update the textview, but the weird thing is this combination: it works in debug but not live, but another texview and a canvas are updated ok. Even a log message (placed in the same if clause as this textview update) works in debug but not when run on emulator (other log messages are being
displayed ok).
The layout also has a canvas used for a simple game and the relevant code is within the running of that game. The canvas is updated by a repeating runnable. I wanted to add a time limit to the game so thought I'd simply mark the start time and then check in the runnable the time since the start. The textview is intended to show the seconds passing - and it works in debug! Abbreviated version of code below.
Maybe it's something about timing, something not having time to run properly? It doesn't need another runnable, does it, seeing as it's managing to update the other textview ok? Should you do runnables within runnables?
Any help or guidance appreciated.
Layout
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/trees2"
android:orientation="vertical"
tools:context=".GameActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal" >
<TextView
android:id="#+id/score"
android:layout_width="54dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical|center_horizontal"
android:text="0"
android:background="#drawable/border"
android:textAppearance="?android:attr/textAppearanceLarge" >
</TextView>
<TextView
android:id="#+id/countdown"
android:layout_width="54dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical|center_horizontal"
android:text="0"
android:background="#drawable/border"
android:textAppearance="?android:attr/textAppearanceLarge" >
</TextView>
</LinearLayout>
<TextView
android:id="#+id/the_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="" />
<couk.jit.drawing.GameBoard
android:id="#+id/the_canvas"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="20dip" />
</LinearLayout>
GameBoard.java
public class GameBoard extends View {
public GameBoard(Context context, AttributeSet aSet) {
super(context, aSet);
p = new Paint();
sprite1 = new Point(-1,-1);
m = new Matrix();
bm1 = BitmapFactory.decodeResource(getResources(), R.drawable.b1);
sprite1Bounds = new Rect(0,0, bm1.getWidth(), bm1.getHeight());
bm = BitmapFactory.decodeResource(getResources(),R.drawable.go_sign);
}
synchronized public boolean didItHitTarget() {
if .... return true;
else ... return false;
}
#Override
synchronized public void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), p);
canvas.drawBitmap(bm3, sprite3.x, sprite3.y, null);
}
}
GameActivity.java
public class GameActivity extends Activity implements OnTouchListener {
private static final int FRAME_RATE = 20;
long startTime;
int timerCount = 0;
int score = 0;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
h.postDelayed(new Runnable() {
#Override
public void run() {
initGfx();
}
}, 1000);
}
synchronized public void initGfx() {
Point p1, p2;
p1 = getStartPoint();
((GameBoard)findViewById(R.id.the_canvas)).setSprite1(p1.x, p1.y);
((View)findViewById(R.id.the_canvas)).setOnTouchListener(this);
frame.removeCallbacks(frameUpdate);
frame.postDelayed(frameUpdate, FRAME_RATE);
}
private Runnable frameUpdate = new Runnable() {
#Override
synchronized public void run() {
if (state == GameState.Ready)
updateReady();
if (state == GameState.Running)
updateRunning();
}
synchronized public void updateReady() {
frame.removeCallbacks(frameUpdate);
((GameBoard)findViewById(R.id.the_canvas)).invalidate();
startTime = System.nanoTime(); //set game start time
frame.postDelayed(frameUpdate, FRAME_RATE);
}
synchronized public void updateRunning() {
frame.removeCallbacks(frameUpdate);
if (((GameBoard)findViewById(R.id.the_canvas)).didItHitTarget()) {
score = score + 1;
//This works fine
((TextView)findViewById(R.id.score)).setText(Integer.toString(score));
Point p1;
p1 = getStartPoint();
((GameBoard)findViewById(R.id.the_canvas)).setSprite1(p1.x, p1.y);
}
float deltaTime = (System.nanoTime() - startTime) / 100000000000.0f;
int j = (int) deltaTime;
if ((int)deltaTime > timerCount) {
timerCount = timerCount + 1;
//This works in debug but does not update live
((TextView)findViewById(R.id.countdown)).setText(Integer.toString(timerCount));
Log.i(TAG, "Updating countdown");
}
((GameBoard)findViewById(R.id.the_canvas)).invalidate();
frame.postDelayed(frameUpdate, FRAME_RATE);
}
};
This was my solution in case anyone interested (or can still suggest better way!).
Going from blackbelt's point that certain parts of the code are simply not being entered, I thought that, although I couldn't find the reason, it must be something to do with the complexity of the timer clock combined with android's thread handling. So I created the timer using a separate thread, as in code here:
Android update TextView in Thread and Runnable