Chronometer with millisecond–Custom component

As part of my new application, that i will publish on the market soon, i had to face the problem of creating/using a chronometer. What i need is a chronometer that can handle milliseconds. The first idea it was to use the Chronometer widget shipped with Android OS but it has a big limit, it can’t handle milliseconds it can just show seconds, minutes and hours. So it wasn’t the right component to use in my case. I looked around on the net and i found different solutions and in this post i want to create a custom component that everyone can use when there’s this kind of needs.
In the first part of this post i will show how to create the Chronometer and the second how we can implement a custom component.
What do we need?…We need:

  • a Timer
  • a TimerTask
  • a class that implements the Runnable interface

With the Timer we schedule a TimerTask that executes the thread that update the view with the elapsed milliseconds. We need, moreover, two methods: one to start the chronometer and another one to stop it.
Here the code:

public void start() {
    timer = new Timer();
    startTime = System.currentTimeMillis();
    timer.scheduleAtFixedRate(new TimerTask() {
        
        @Override
        public void run() {
            runThread();                
        }
    }, delayTime, intervalTime);
    
}

Let’s analyze it. First we create a timer and save the current time in milliseconds so we can calculate the elapsed time. The second step is simply schedule at specific time interval a TimerTask that will update the view. If you notice the scheduleAtFixedRate there is another paramter, called delayTime, and it is the delay the timer should start. In our case it will be always 0.

In the TimerTask we simply call a runThread() method. This method is:

private void runThread() {
    ((Activity) ctx).runOnUiThread(new MyRunner());        
}

Remember that we are implementing a custom component so we need to cast ctx (Context) to the Activity. MyRunner is the class that implements the Runnable interface and it is the one that does the job to update the interface. runOnUiThread method is defined in the Android OS API as “Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.”. If we don’t use this method and update directly the view we will get an error. Now in MyRunner class we simply update the text with the elapsed milliseconds.

private class MyRunner  implements Runnable {

    @Override
    public void run() {            
        long elapsed = System.currentTimeMillis() - startTime;
        long sec = 0;
        long mill = 0;
        
        if (elapsed >= 1000) {
            sec = elapsed / 1000;
            mill = elapsed % 1000;
        }
        else 
            mill = elapsed;
        
        Chronometer.this.setText(sec + "." + mill);
    }
    
}

 

Create a custom component

At this point we have everything we need to create a custom component that we can use in our UI. This custom component can be derived directly from the TextView because it needs only to write the elapsed time. So we have

public class Chronometer extends TextView {
    
    private Timer timer;
    private Context ctx;
    private long startTime;
    private long delayTime;
    private long intervalTime;
    
    
    public Chronometer(Context context) {
        super(context);
        this.ctx = context;
    }

    public Chronometer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.ctx = context;
        initAttrs(attrs);
    }

    public Chronometer(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.ctx = context;
        initAttrs(attrs);
    }
    ......
}

We need, moreover, to add two custom attributes called delayTime and intervalTime so we can fully customize this custom view. To do it, we have to add a file called attrs.xml under the values directroy. This file look like the one shown below:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="com.survivingwithandroid.chronometer.Chronometer">
            <attr name="delayTime" format="integer" />
            <attr name="intervalTime" format="integer" />        
    </declare-styleable>
</resources>

We can assume that these two custom parameters are integer.

Test the component

If we want to test the component we simply need to create an activity and a layout xml file.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:swa="http://schemas.android.com/apk/res/com.survivingwithandroid.chronometer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
     >

    <TextView
        android:id="@+id/textViewLabel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="24dp"
        android:text="Elapsed Time" />

    <com.survivingwithandroid.chronometer.Chronometer
        android:id="@+id/chron"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/textView1"
        android:layout_centerHorizontal="true"
        android:textAppearance="?android:attr/textAppearanceMedium"
        swa:delayTime="0"
        swa:intervalTime="1" />

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="32dp"
        android:layout_toRightOf="@+id/textViewLabel"
        android:text="Start" />

    <Button
        android:id="@+id/btnStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/btnStart"
        android:layout_alignBottom="@+id/btnStart"
        android:layout_alignLeft="@+id/textViewTime"
        android:text="Stop" />

</RelativeLayout>

Notice how we added a new name space called

xmlns:swa=”http://schemas.android.com/apk/res/com.survivingwithandroid.chronometer

We added two button to start and stop the chronometer. Our main Activity is really simple and shown below:

package com.survivingwithandroid.chronometer;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button btnStart = (Button) findViewById(R.id.btnStart);
        Button btnStop = (Button) findViewById(R.id.btnStop);
        final Chronometer chron = (Chronometer) findViewById(R.id.chron);
        
        btnStart.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                chron.start();
                
            }
        });
        
        btnStop.setOnClickListener(new View.OnClickListener() {
            
             @Override
             public void onClick(View v) {
                 chron.stop();
                 
             }
         });
            
    }
}
  • Tunarock

    nice work, but at a certain point the chronometer stucks, and doesn't refresh the view at every millisec