2

I writing a simple chess clock app based on android.widget.Chronometer. I'm using it with setCountDown(true) flag and all logic working well. Each Chronometer creating in a separate Fragment. But sometimes there are some problems with the chronometer initial value. For example, I'm setting the initial value for 20 seconds but in reality I have this:enter image description here And on app restarting this distinguishing may be on random clock. I'm setting initial value this way, in my class that extending android.widget.Chronometer, and mTimeLimit equals 20000 each times:

private void setTimeLimit() {
    mStartTime = SystemClock.elapsedRealtime() + mTimeLimit;
    setBase(mStartTime);
}

I'm thinking that problem may be in fragment creating time or so on. Who knows what I'm getting wrong?

aminography
  • 21,986
  • 13
  • 70
  • 74
Stanislav Batura
  • 420
  • 4
  • 11

2 Answers2

0

I'm find a one solution by adding 100 millis on initial value:

mStartTime = SystemClock.elapsedRealtime() + mTimeLimit + 100;

Seems that it solves a problem, but i think it's not a best decision, maybe somebody knows better approach.

Stanislav Batura
  • 420
  • 4
  • 11
0

The problem happens because the Chronometer class calculates seconds by truncating the division of milliseconds, when it should round the result.

This is not a problem when counting up, but it is when counting down.

I opened an issue on the issue tracker to request the modification of the calculation in the Chronometer class:

https://issuetracker.google.com/issues/297733248

Adding an arbitrary amount of milliseconds to the base of the chronometer might work in some devices, but in slow devices the problem might still happen.

My solution (while the issue gets resolved in the issue tracker) was to create a custom 'MyChronometer' class, based on the framework's Chronometer class, but with the correct calculation (see calculation in the 'updateText' method):

@SuppressLint("AppCompatCustomView")
@RemoteView
public class MyChronometer extends TextView {

    public interface OnChronometerTickListener {
        void onChronometerTick(MyChronometer chronometer);
    }

    private long mBase;
    private boolean mVisible;
    private boolean mStarted;
    private boolean mRunning;
    private final StringBuilder mRecycle = new StringBuilder(8);
    private OnChronometerTickListener mOnChronometerTickListener;
    private boolean mCountDown;

    public MyChronometer(Context context) {
        this(context, null, 0);
    }

    public MyChronometer(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyChronometer(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public MyChronometer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        mBase = SystemClock.elapsedRealtime();
        updateText(mBase);
    }

    public void setCountDown(boolean countDown) {
        mCountDown = countDown;
        updateText(SystemClock.elapsedRealtime());
    }

    public void setBase(long base) {
        mBase = base;
        dispatchChronometerTick();
        updateText(SystemClock.elapsedRealtime());
    }

    public long getBase() {
        return mBase;
    }

    public void setOnChronometerTickListener(OnChronometerTickListener listener) {
        mOnChronometerTickListener = listener;
    }

    public OnChronometerTickListener getOnChronometerTickListener() {
        return mOnChronometerTickListener;
    }

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

    public void stop() {
        mStarted = false;
        updateRunning();
    }

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

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

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        updateRunning();
    }

    private synchronized void updateText(long now) {

        long diffMillis = mCountDown ? mBase - now : now - mBase;
        int seconds = Math.round(diffMillis / 1000f);

        String text;

        if (seconds < 0) {
            text = "-" + DateUtils.formatElapsedTime(mRecycle, -seconds);
        } else {
            text = DateUtils.formatElapsedTime(mRecycle, seconds);
        }

        setText(text);

    }

    private void updateRunning() {

        boolean running = mVisible && mStarted && isShown();

        if (running != mRunning) {

            if (running) {
                updateText(SystemClock.elapsedRealtime());
                dispatchChronometerTick();
                postDelayed(mTickRunnable, 1000);
            } else {
                removeCallbacks(mTickRunnable);
            }

            mRunning = running;

        }

    }

    private final Runnable mTickRunnable = new Runnable() {
        @Override
        public void run() {
            if (mRunning) {
                updateText(SystemClock.elapsedRealtime());
                dispatchChronometerTick();
                postDelayed(mTickRunnable, 1000);
            }
        }
    };

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

    @Override
    public CharSequence getAccessibilityClassName() {
        return MyChronometer.class.getName();
    }

}
jmart
  • 2,769
  • 21
  • 36