Android: chronometer as a persistent stopwatch. Ho

2019-01-09 08:15发布

问题:

I do have one service running in the background. Whenever it starts I store in memory the starting time in milliseconds:

startingTime = new Date().getTime();

I want to display a chronometer that starts counting when the service starts and never stops until the user presses a button. I want to allow the user to leave the activity rendering the chronometer, do some stuff and then return. But the idea is that when the user returns I dont want the chronometer to go to 0:00 again. Insted I want it to show the exact time that has passed ever since the service has started.

I can calculate elapsedTime every time the user return to the chronometer activity:

elapsedTime =  new Date().getTime() - startingTime;

The thing is that i dont know how to tell the chronometer to start counting from that time!

Setting it as the chronometer base does not work. Can someon explain what exactly "base" means or how to accomplish this?

thanks a lot! BYE

回答1:

You can use Chronometer.

You should also check this thread.

EDIT: The solution:

public class ChronoExample extends Activity {
 Chronometer mChronometer;

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

     LinearLayout layout = new LinearLayout(this);
     layout.setOrientation(LinearLayout.VERTICAL);

     mChronometer = new Chronometer(this);

     // Set the initial value
     mChronometer.setText("00:10");
     layout.addView(mChronometer);

     Button startButton = new Button(this);
     startButton.setText("Start");
     startButton.setOnClickListener(mStartListener);
     layout.addView(startButton);

     Button stopButton = new Button(this);
     stopButton.setText("Stop");
     stopButton.setOnClickListener(mStopListener);
     layout.addView(stopButton);

     Button resetButton = new Button(this);
     resetButton.setText("Reset");
     resetButton.setOnClickListener(mResetListener);
     layout.addView(resetButton);        

     setContentView(layout);
 }

 private void showElapsedTime() {
     long elapsedMillis = SystemClock.elapsedRealtime() - mChronometer.getBase();            
     Toast.makeText(ChronoExample.this, "Elapsed milliseconds: " + elapsedMillis, 
             Toast.LENGTH_SHORT).show();
 }

 View.OnClickListener mStartListener = new OnClickListener() {
     public void onClick(View v) {
      int stoppedMilliseconds = 0;

         String chronoText = mChronometer.getText().toString();
         String array[] = chronoText.split(":");
         if (array.length == 2) {
           stoppedMilliseconds = Integer.parseInt(array[0]) * 60 * 1000
               + Integer.parseInt(array[1]) * 1000;
         } else if (array.length == 3) {
           stoppedMilliseconds = Integer.parseInt(array[0]) * 60 * 60 * 1000 
               + Integer.parseInt(array[1]) * 60 * 1000
               + Integer.parseInt(array[2]) * 1000;
         }

         mChronometer.setBase(SystemClock.elapsedRealtime() - stoppedMilliseconds);
         mChronometer.start();
     }
 };

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

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


回答2:

The base time is the time that the Chronometer started ticking at. You can set it using Chronometer.setBase(). You should get the base time by using SystemClock.getElapsedTime(). Call setBase() with the start time each time the Chronometer is started. If there is potential for the Activity to be destroyed and recreated while the timer is still active then you will need to hold the base time somewhere outside of the Activity that owns the Chronometer.



回答3:

To start the chronometer you should use the setBase() method of your chronometer with SystemClock.elapsedRealTime(). Just like this:

mChronometer.setBase(SystemClock.elapsedRealTime())

But if you want to start at another time, you have to subtract the time you want in milliseconds. For example, you want to start your Chronometer at 10 seconds:

mChronometer.setBase(SystemClock.elapsedRealTime() - 10*1000);

At 2 minutes:

mChronometer.setBase(SystemClock.elapsedRealTime() - 2*60*1000);

But the problem here is that the Chronometer will show "00:00" time before it start to count, to change it to your time you have to do that :

mChronometer.setText("02:00");


回答4:

Some strange with SystemClock.getElapsedTime(), I did some changes for normal using with start date, like

myChron.setBase(startDate.getTime());

Here child of Chronometer below, TimeView

import android.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Chronometer;
import android.widget.RemoteViews;

import java.util.Formatter;
import java.util.IllegalFormatException;
import java.util.Locale;

@RemoteViews.RemoteView
public class TimeView extends Chronometer {
    private static final String TAG = "TimeView";

    private long mBase;
    private boolean mVisible;
    private boolean mStarted;
    private boolean mRunning;
    private boolean mLogged;
    private String mFormat;
    private Formatter mFormatter;
    private Locale mFormatterLocale;
    private Object[] mFormatterArgs = new Object[1];
    private StringBuilder mFormatBuilder;
    private OnChronometerTickListener mOnChronometerTickListener;
    private StringBuilder mRecycle = new StringBuilder(8);

    private static final int TICK_WHAT = 2;

    /**
     * Initialize this Chronometer object.
     * Sets the base to the current time.
     */
    public TimeView(Context context) {
        this(context, null, 0);
    }

    /**
     * Initialize with standard view layout information.
     * Sets the base to the current time.
     */
    public TimeView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Initialize with standard view layout information and style.
     * Sets the base to the current time.
     */
    public TimeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mBase = System.currentTimeMillis();
        updateText(mBase);
    }

    public void setBase(long base) {
        mBase = base;
        dispatchChronometerTick();
        updateText(System.currentTimeMillis());
    }

    /**
     * Return the base time as set through {@link #setBase}.
     */
    public long getBase() {
        return mBase;
    }

    public void start() {
        mStarted = true;
        updateRunning();
    }

    /**
     * Stop counting up.  This does not affect the base as set from {@link #setBase}, just
     * the view display.
     * <p/>
     * This stops the messages to the handler, effectively releasing resources that would
     * be held as the chronometer is running, via {@link #start}.
     */
    public void stop() {
        mStarted = false;
        updateRunning();
    }

    /**
     * The same as calling {@link #start} or {@link #stop}.
     *
     * @hide pending API council approval
     */
    public void setStarted(boolean started) {
        mStarted = started;
        updateRunning();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mVisible = false;
        updateRunning();
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        mVisible = visibility == VISIBLE;
        updateRunning();
    }

    private synchronized void updateText(long now) {
        long seconds = now - mBase;
        seconds /= 1000;
        String text = DateUtils.formatElapsedTime(mRecycle, seconds);

        if (mFormat != null) {
            Locale loc = Locale.getDefault();
            if (mFormatter == null || !loc.equals(mFormatterLocale)) {
                mFormatterLocale = loc;
                mFormatter = new Formatter(mFormatBuilder, loc);
            }
            mFormatBuilder.setLength(0);
            mFormatterArgs[0] = text;
            try {
                mFormatter.format(mFormat, mFormatterArgs);
                text = mFormatBuilder.toString();
            } catch (IllegalFormatException ex) {
                if (!mLogged) {
                    Log.w(TAG, "Illegal format string: " + mFormat);
                    mLogged = true;
                }
            }
        }
        setText(text);
    }

    private void updateRunning() {
        boolean running = mVisible && mStarted;
        if (running != mRunning) {
            if (running) {
                updateText(System.currentTimeMillis());
                dispatchChronometerTick();
                mHandler.sendMessageDelayed(Message.obtain(mHandler, TICK_WHAT), 1000);
            } else {
                mHandler.removeMessages(TICK_WHAT);
            }
            mRunning = running;
        }
    }

    private Handler mHandler = new Handler() {
        public void handleMessage(Message m) {
            if (mRunning) {
                updateText(System.currentTimeMillis());
                dispatchChronometerTick();
                sendMessageDelayed(Message.obtain(this, TICK_WHAT), 1000);
            }
        }
    };

    void dispatchChronometerTick() {
        if (mOnChronometerTickListener != null) {
            mOnChronometerTickListener.onChronometerTick(this);
        }
    }
}

Just copy and use, it works for me



回答5:

When you set the basetime with .setBase(SystemClock.elapsedRealTime()) the chronomether starts to count from 00.00 but the time it stores is the amount of milisecs from boot. When you use .stop the internal count does not stop, just the time you see on the clock. So, if you use .start again, the clock count jumps to the real count. If you want to store the time that has passed from the start, you have to get again the System elapsed time and make the difference with the .setTime



回答6:

How to set starting time? What is Chronometer “Base”?

Use SystemClock.elapsedRealtime() for this purpose:

   myChronometer.setBase(SystemClock.elapsedRealtime());


回答7:

This works for me :

Date now = new Date();
long elapsedTime = now.getTime() - startTime.getTime(); //startTime is whatever time you want to start the chronometer from. you might have stored it somwehere
myChronometer.setBase(SystemClock.elapsedRealtime() - elapsedTime);
myChronometer.start();