Approach 1: Chronometer
In Android, Chronometer
is a class that implements a simple timer. Chronometer
is a subclass of TextView
. This class helps us to add a timer in our app.
You can give start time in the elapsedRealTime()
timebase and it start counting from that. If we don’t give base time then it will use the time at which time we call start()
method. By default a Chronometer
displays the current timer value in the form of MM:SS or H:MM:SS. We can set our own format into an arbitrary string.
Let’s start with the interface:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal"> <Chronometer android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:id="@+id/cmTimer" /> <Button android:id="@+id/btnStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Start" android:onClick="onClick" /> <Button android:id="@+id/btnStop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Stop" android:onClick="onClick" /> <Button android:id="@+id/btnReset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Reset" android:onClick="onClick" /> </LinearLayout>
Let’s discuss some important methods of Chronometer
that may be called in order to manage the chronometer.
start()
: start function of chronometer is used to start the counting up.stop()
: stop function of chronometer is used to stop the counting up.setFormat(String format)
: set format function of chronometer is used to set the format string used to display. In other words we can say it is used to display text, numbers etc along-with chronometer.setOnChronometerTickListener(Chronometer.OnChronometerTickListener listener)
. This is a listener event which is automatically called when the chronometer changes.setBase(long base)
: set base function of chronometer is used to set the time that count up time is in reference to. You can give it a start time in the elapsedRealtime()
timebase, and it counts up from that, or if you don’t give it a base time, it will use the time at which you call start()
. SystemClock.elapsedRealtime()
this returns the time in milliseconds since your device booted included sleep time. This means that if you test your app on an emulator and the emulator opened 30 minutes ago, this method will return 1800000 milliseconds (30 min).Following is our Activity.
public class MainActivity extends AppCompatActivity { Chronometer cmTimer; Button btnStart, btnStop, btnReset; Boolean resume = false; long elapsedTime; String TAG = "TAG"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); cmTimer = (Chronometer) findViewById(R.id.cmTimer); btnStart = (Button) findViewById(R.id.btnStart); btnStop = (Button) findViewById(R.id.btnStop); btnReset = (Button) findViewById(R.id.btnReset); // example setOnChronometerTickListener cmTimer.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() { public void onChronometerTick(Chronometer arg0) { if (!resume) { long minutes = ((SystemClock.elapsedRealtime() - cmTimer.getBase())/1000) / 60; long seconds = ((SystemClock.elapsedRealtime() - cmTimer.getBase())/1000) % 60; elapsedTime = SystemClock.elapsedRealtime(); Log.d(TAG, "onChronometerTick: " + minutes + " : " + seconds); } else { long minutes = ((elapsedTime - cmTimer.getBase())/1000) / 60; long seconds = ((elapsedTime - cmTimer.getBase())/1000) % 60; elapsedTime = elapsedTime + 1000; Log.d(TAG, "onChronometerTick: " + minutes + " : " + seconds); } } }); } public void onClick(View v) { switch(v.getId()) { case R.id.btnStart: btnStart.setEnabled(false); btnStop.setEnabled(true); if (!resume) { cmTimer.setBase(SystemClock.elapsedRealtime()); cmTimer.start(); } else { cmTimer.start(); } break; case R.id.btnStop: btnStart.setEnabled(true); btnStop.setEnabled(false); cmTimer.stop(); cmTimer.setText(""); resume = true; btnStart.setText("Resume"); break; case R.id.btnReset: cmTimer.stop(); cmTimer.setText("00:00"); resume = false; btnStop.setEnabled(false); break; } } }
The start button can be clicked in two scenarios:
Chronometer
for the first time:Chronometer
, which is the time in Milliseconds from where the Chronometer
starts counting from. We set it to SystemClock.elapsedRealtime()
. Which is the time in Milliseconds since the device boot (equivalent to current time).Chronometer
again to resume counting from the value it had when it was stopped.The stop button pauses the Chronometer
. And sets the resume
flag to true
to indicate that the next click on the start button will resume counting.
The reset button resets the chronometer so the next click on start button will start counting from zero.
Result
Approach 2: Handler
In this example we are going to create a simple Android Timer application. We are going to use some very basic ideas and tools, like Handler, that you can use in many cases in your Applications.
Following layout is very simple
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="5dp"> <TextView android:text="00:00:00" android:id="@+id/tvTimer" android:textSize="40sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tvTimer" android:layout_alignParentLeft="true" android:onClick="start" android:text="Start"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tvTimer" android:layout_alignParentRight="true" android:onClick="stop" android:text="Stop"/> </RelativeLayout>
Following is MainActivity.java
public class SixActivity extends AppCompatActivity { TextView tvTimer; long startTime, timeInMilliseconds = 0; Handler customHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_six); tvTimer = (TextView) findViewById(R.id.tvTimer); } public static String getDateFromMillis(long d) { SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); df.setTimeZone(TimeZone.getTimeZone("GMT")); return df.format(d); } public void start(View v) { startTime = SystemClock.uptimeMillis(); customHandler.postDelayed(updateTimerThread, 0); } public void stop(View v) { customHandler.removeCallbacks(updateTimerThread); } private Runnable updateTimerThread = new Runnable() { public void run() { timeInMilliseconds = SystemClock.uptimeMillis() - startTime; tvTimer.setText(getDateFromMillis(timeInMilliseconds)); customHandler.postDelayed(this, 1000); } }; }
The code of this section is pretty much self explanatory. The interesting part is how to update the value of the timer. We are going to use a Handler
for that. The Handler
takes a Runnable
object and it schedules its execution; it places the runnable process as a job in an execution queue to be run after a specified amount of time. The Runnable
will be run on the thread to which this handler is attached.
Result
Approach 3: Reverse of CountDownTimer
We can use a CountDownTimer in reverse and get the time elapsed.
long totalSeconds = 30; long intervalSeconds = 1; CountDownTimer timer = new CountDownTimer(totalSeconds * 1000, intervalSeconds * 1000) { public void onTick(long millisUntilFinished) { Log.d(TAG, "Elapsed: " + (totalSeconds * 1000 - millisUntilFinished) / 1000); } public void onFinish() { Log.d(TAG, "Time's up!"); } };
To start the timer.
timer.start()
To stop the timer.
timer.cancel()
How to set Chronometer base time from timestamp?
long elapsedRealtimeOffset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); cmTimer.setBase(timestamp - elapsedRealtimeOffset); cmTimer.start();