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

3

14

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.

Arcboutant answered 24/2, 2014 at 7:49 Comment(2)
The right thing to do is using a Timer.Guitarist
You can't sleep the main thread. And if you run this code in other thread, you are going to need a handler in order to write to UI (only the main thread can change the UI).Margrettmarguerie
T
7

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);
}
Toner answered 24/2, 2014 at 9:51 Comment(2)
@Ammaraly only OP knows, I modified his code in the first place.Toner
Chronometer doesn't support pause and resume, which stopwatches need.Paramount
J
18

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>
Jeminah answered 24/2, 2014 at 10:5 Comment(2)
wouldn't it be nicer if you also post a UI screenshot :)Bergh
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?Margrettmarguerie
T
7

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);
}
Toner answered 24/2, 2014 at 9:51 Comment(2)
@Ammaraly only OP knows, I modified his code in the first place.Toner
Chronometer doesn't support pause and resume, which stopwatches need.Paramount
C
1

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.

Cole answered 24/2, 2014 at 9:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.