Is there already a StopWatch class for android and why doesn't my implementation work?

25,802

Solution 1

You should use a Chronometer.

But anyway, your code can work if you remove the sleep from UI thread.

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));
            handler.postDelayed(runnable, 1000L);
        }
    }

};

private Hanlder mHandler;
private boolean mStarted;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mHandler = new Handler();
}

@Override
protected void onStart() {
    super.onStart();
    mStarted = true;
    mHandler.postDealyed(runnable, 1000L);
}

@Override
protected void onStop() {
    super.onStop();
    mStarted = false;
    mHandler.removeCallbacks(mRunnable);
}

Solution 2

Have a look at the Chronometer class.

Sample code from APIDemo:

import android.app.Activity;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Chronometer;

public class ChronometerDemo extends Activity {
    Chronometer mChronometer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.chronometer);

        Button button;

        mChronometer = (Chronometer) findViewById(R.id.chronometer);

        // Watch for button clicks.
        button = (Button) findViewById(R.id.start);
        button.setOnClickListener(mStartListener);

        button = (Button) findViewById(R.id.stop);
        button.setOnClickListener(mStopListener);

        button = (Button) findViewById(R.id.reset);
        button.setOnClickListener(mResetListener);

        button = (Button) findViewById(R.id.set_format);
        button.setOnClickListener(mSetFormatListener);

        button = (Button) findViewById(R.id.clear_format);
        button.setOnClickListener(mClearFormatListener);
    }

    View.OnClickListener mStartListener = new OnClickListener() {
        public void onClick(View v) {
            mChronometer.start();
        }
    };

    View.OnClickListener mStopListener = new OnClickListener() {
        public void onClick(View v) {
            mChronometer.stop();
        }
    };

    View.OnClickListener mResetListener = new OnClickListener() {
        public void onClick(View v) {
            mChronometer.setBase(SystemClock.elapsedRealtime());
        }
    };

    View.OnClickListener mSetFormatListener = new OnClickListener() {
        public void onClick(View v) {
            mChronometer.setFormat("Formatted time (%s)");
        }
    };

    View.OnClickListener mClearFormatListener = new OnClickListener() {
        public void onClick(View v) {
            mChronometer.setFormat(null);
        }
    };
}

R.layout.chronometer:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:padding="4dip"
    android:gravity="center_horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Chronometer android:id="@+id/chronometer"
        android:format="@string/chronometer_initial_format"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="0"
        android:paddingBottom="30dip"
        android:paddingTop="30dip"
        />

    <Button android:id="@+id/start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="@string/chronometer_start">
        <requestFocus />
    </Button>

    <Button android:id="@+id/stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="@string/chronometer_stop">
    </Button>

    <Button android:id="@+id/reset"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="@string/chronometer_reset">
    </Button>

    <Button android:id="@+id/set_format"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="@string/chronometer_set_format">
    </Button>

    <Button android:id="@+id/clear_format"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="@string/chronometer_clear_format">
    </Button>

</LinearLayout>

Add this to Strings.xml

<string name="chronometer_start">Start</string>
<string name="chronometer_stop">Stop</string>
<string name="chronometer_reset">Reset</string>
<string name="chronometer_set_format">Set format string</string>
<string name="chronometer_clear_format">Clear format string</string>
<string name="chronometer_initial_format">Initial format: <xliff:g id="initial-format">%s</xliff:g></string>

Solution 3

You should use a Chronometer.

Chronometer mChronometer;
mChronometer = (Chronometer) findViewById(R.id.chronometer1);
long timeWhenStopped = 0;

when we stop the chronometer

timeWhenStopped = mChronometer.getBase() - SystemClock.elapsedRealtime();
mChronometer.stop();

the chronometer before starting it

mChronometer.setBase(SystemClock.elapsedRealtime() + timeWhenStopped);
mChronometer.start();

resetting the chronometer

mChronometer.setBase(SystemClock.elapsedRealtime());
timeWhenStopped = 0;

Solution 4

Why your code doesn't work is because you post a Runnable to the UI thread message queue. The runnable runs forever in the while loop, preventing the UI thread from doing any other business, such as drawing your layout and responding to inputs.

Using Thread.sleep() in an UI thread is almost always wrong. Consider using a Timer instead.

Share:
25,802
principal-ideal-domain
Author by

principal-ideal-domain

Updated on May 31, 2020

Comments

  • principal-ideal-domain
    principal-ideal-domain almost 4 years

    Lately I saw http://developer.android.com/reference/android/os/CountDownTimer.html and wondered if there is an respective class for stop watches since I want to tell the user of my App how long he's already trying to solve the puzzle. I mean it isn't that complicated to program such a stop watch on your own. I tried something like

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                    long seconds = (System.currentTimeMillis() - t) / 1000;
                    statusBar.setText(String.format("%02d:%02d", seconds / 60, seconds % 60));
                }
            }
    
        };
        statusBar.post(runnable);
    

    But bizarrely the layout of my activity isn't inflated anymore since I have this statusBar.post(runnable); in the end of the acitivity's onCreate method meaning that after starting the app I only see a white screen instead of the normal gui.

  • AZ_
    AZ_ over 9 years
    wouldn't it be nicer if you also post a UI screenshot :)
  • Rudolf Real
    Rudolf Real over 9 years
    the string chronometer_initial_format is missing, could you please add it in this same answer so we avoid going to Google to see the formats?
  • Yaroslav Mytkalyk
    Yaroslav Mytkalyk about 9 years
    @Ammaraly only OP knows, I modified his code in the first place.
  • Jack
    Jack about 3 years
    Chronometer doesn't support pause and resume, which stopwatches need.