Android: CountDownTimer saute last onTick ()!

Code:

public class SMH extends Activity {  

    public void onCreate(Bundle b) {  
        super.onCreate(b);  
        setContentView(R.layout.main);  

        TextView tv = (TextView) findViewById(R.id.tv);  

        new CountDownTimer(10000, 2000) {  
            public void onTick(long m) {  
               long sec = m/1000+1;  
               tv.append(sec+" seconds remainn");  
            }  
            public void onFinish() {  
               tv.append("Done!");  
            }  
        }.start();  
   }

Sortie:

Il reste 10 Secondes!--6-->
Il reste 8 secondes!--6-->
Il reste 6 secondes!--6-->
Il reste 4 secondes!--6-->
Fait!

Problème:

Comment puis-je obtenir pour afficher "2 secondes restent"? Le temps écoulé est en effet de 10 secondes, mais le dernier onTick () n'arrive jamais. Si je change le deuxième paramètre de 2000 à 1000, alors c'est le sortie:

10 secondes restent

Il reste 9 secondes!--6-->
Il reste 8 secondes!--6-->
Il reste 7 secondes!--6-->
Il reste 6 secondes!--6-->
Il reste 5 secondes!--6-->
Il reste 4 secondes!--6-->
Il reste 3 secondes!--6-->
2 secondes restent

Fait!

Donc, vous voyez, il semble ignorer cette dernière onTick (). Et btw, le fichier XML est essentiellement le main par défaut.xml avec le TextView assigné le id tv et le texte "".

43
demandé sur Jeremy D 2012-01-14 01:39:59

12 réponses

Je ne sais pas pourquoi le dernier TIC ne fonctionne pas mais vous pouvez créer votre propre timer avec Runable , par exemple.

class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
        }
    public void Start() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                if(millisInFuture <= 0) {
                    Log.v("status", "done");
                } else {
                    long sec = millisInFuture/1000;
                    Log.v("status", Long.toString(sec) + " seconds remain");
                    millisInFuture -= countDownInterval;
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

et pour le démarrer, il

new MyCountDownTimer(10000, 2000).Start();

EDIT FOR GOOFY'S QUESTION

vous devez avoir une variable pour maintenir l'état du compteur (booléen) . ensuite, vous pouvez écrire une méthode Stop() comme Start().

EDIT-2 POUR LA QUESTION DE GOOFY

en fait, il n'y a pas de bug sur le compteur d'arrêt mais il y a un bug sur recommencer après l'arrêt(reprendre).

j'écris une nouvelle mise à jour du code complet que je viens d'essayer et qui fonctionne. C'est un compteur de base, qui montrent sur l'écran avec le bouton démarrer et arrêter.

classe pour compteur

public class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    private boolean status;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
            status = false;
            Initialize();
    }

    public void Stop() {
        status = false;
    }

    public long getCurrentTime() {
        return millisInFuture;
    }

    public void Start() {
        status = true;
    }
    public void Initialize() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                long sec = millisInFuture/1000;
                if(status) {
                    if(millisInFuture <= 0) {
                        Log.v("status", "done");
                    } else {
                        Log.v("status", Long.toString(sec) + " seconds remain");
                        millisInFuture -= countDownInterval;
                        handler.postDelayed(this, countDownInterval);
                    }
                } else {
                    Log.v("status", Long.toString(sec) + " seconds remain and timer has stopped!");
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

classe d'activité

public class CounterActivity extends Activity {
    /** Called when the activity is first created. */
    TextView timeText;
    Button startBut;
    Button stopBut;
    MyCountDownTimer mycounter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        timeText = (TextView) findViewById(R.id.time);
        startBut = (Button) findViewById(R.id.start);
        stopBut = (Button) findViewById(R.id.stop);
        mycounter = new MyCountDownTimer(20000, 1000);
        RefreshTimer();
    }

    public void StartTimer(View v) {
        Log.v("startbutton", "saymaya basladi");
        mycounter.Start();
    }

    public void StopTimer(View v) {
        Log.v("stopbutton", "durdu");
        mycounter.Stop();
    }

    public void RefreshTimer() 
    {
        final Handler handler = new Handler();
        final Runnable counter = new Runnable(){

            public void run(){
                timeText.setText(Long.toString(mycounter.getCurrentTime()));
                handler.postDelayed(this, 100);
            }
        };

        handler.postDelayed(counter, 100);
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:weightSum="1">
    <TextView android:textAppearance="?android:attr/textAppearanceLarge" 
              android:text="TextView" android:layout_height="wrap_content" 
              android:layout_width="wrap_content" 
              android:id="@+id/time">
    </TextView>
    <Button android:text="Start" 
            android:id="@+id/start" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StartTimer">
    </Button>
    <Button android:text="Stop" 
            android:id="@+id/stop" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StopTimer">
    </Button>
</LinearLayout>
22
répondu ocanal 2012-03-27 12:47:34

j'ai vérifié le code source de CountDownTimer. La "tique manquante" provient d'une caractéristique spéciale de CountDownTimer que je n'ai pas encore vu être documentée ailleurs:

Au début de chaque tic, avant que onTick() soit appelé, le temps restant jusqu'à la fin du compte à rebours est calculé. Si ce temps est inférieur à l'intervalle de Compte à rebours, onTick est appelé le plus. Au lieu de cela, seule la touche suivante (où la méthode onFinish() sera appelée) est prévu.

étant donné que les horloges matérielles ne sont pas toujours très précises, qu'il peut y avoir d'autres processus en arrière-plan qui retardent le thread de CountDownTimer plus que L'Android lui-même va probablement créer un petit retard lors de l'appel du gestionnaire de messages de CountDownTimer il est plus que probable que l'appel pour le dernier Tic-Tac avant la fin du compte à rebours sera au moins une milliseconde en retard et donc onTick() ne sera pas appelé.

pour mon application j'ai résolu ce problème simplement en rendant les intervalles de ticks "légèrement" plus petits (500 ms)

    myCountDownTimer = new CountDownTimer(countDownTime, intervalTime - 500) {
                                   ...
    }

et je pourrais laisser mon code tel quel. Pour les applications où la durée de l'intervalle est critique, les autres solutions affichées ici sont probablement les meilleures.

47
répondu Nantoka 2012-09-05 14:15:01

la solution la plus simple que j'ai trouvée est la suivante. Notez que cela ne fonctionne que si vous avez besoin d'un écran simple à afficher avec un compte à rebours des secondes.

mTimer = new CountDownTimer(5000, 100){
            public void onTick(long millisUntilFinished) {
                mTimerView.setText(Long.toString(millisUntilFinished/1000));                
             }

             public void onFinish() {
                 mTimerView.setText("Expired");
             }
        };

        mTimer.start();

dans le code au-dessus de onTick() est appelé toutes les 100 millisecondes mais visuellement seules les secondes sont affichées.

4
répondu Zzokk 2012-06-16 16:18:04

j'ai passé des heures à essayer de comprendre ce problème, et je suis heureux de vous montrer un bon travail autour. Ne vous donnez pas la peine d'attendre le onFinish() appeler, il suffit d'ajouter 1 (ou quel que soit votre intervalle) à vos unités, puis Ajouter une instruction if dans le onTick() appels. Il suffit de faire votre onFinish() tâche (s) sur la dernière onTick(). Voici ce que j'ai:

    new CountDownTimer( (countDownTimerValue + 1) * 1000, 1000) { //Added 1 to the countdownvalue before turning it into miliseconds by multiplying it by 1000.
        public void onTick(long millisUntilFinished) {

          //We know that the last onTick() happens at 2000ms remaining (skipping the last 1000ms tick for some reason, so just throw in this if statement.
            if (millisUntilFinished < 2005){ 
                //Stuff to do when finished.
            }else{
                mTextField.setText("Time remaining: " + (((millisUntilFinished) / 1000) - 1));  //My textfield is obviously showing the remaining time. Note how I've had to subtrack 1 in order to display the actual time remaining.
            }
        }

        public void onFinish() {
        //This is when the timer actually finishes (which would be about 1000ms later right? Either way, now you can just ignore this entirely.


        }
    }.start();
3
répondu ajwest 2012-01-30 03:53:42

bien que la solution ci-dessus soit valide, elle peut être améliorée. Il a inutilement un runnable dans une autre classe (qui peut déjà être traité par lui-même). Il suffit donc de créer une classe qui étend un thread (ou runnable).

    class MyTimer extends Thread {
      private long millisInFuture;
      private long countDownInterval;
      final Handler mHandler = new Handler();

      public MyTimer(long pMillisInFuture, long pCountDownInterval) {
        this.millisInFuture = pMillisInFuture;
        this.countDownInterval = pCountDownInterval;
      }

      public void run() {
        if(millisInFuture <= 0) {
          Log.v("status", "done");
        } else {
          millisInFuture -= countDownInterval;
          mHandler.postDelayed(this, countDownInterval);
        }
      }
    }
3
répondu Mike Welsh 2012-03-07 04:02:39

j'ai trouvé une solution facile. J'ai besoin de compte à Rebours de la mise à jour de la ProgressBar, j'ai donc fait ceci:

new CountDownTimer(1000, 100) {

    private int counter = 0;

    @Override
    public void onTick(long millisUntilFinished) {
        Log.d(LOG_TAG, "Tick: " + millisUntilFinished);
        if (++counter == 10) {
            timeBar.setProgress(--lenght); // timeBar and lenght defined in calling code
            counter = 0;
        }
    }


    @Override
    public void onFinish() {
        Log.d(LOG_TAG, "Finish.");

        timeBar.setProgress(0);
    }

};

cocher à faire le tour :)

2
répondu ryabenko-pro 2014-04-30 10:19:43

donc je pense que j'ai un peu exagéré parce que mon timer tourne dans son propre thread au lieu d'utiliser des handlers postDelay, bien qu'il renvoie toujours au thread dans lequel il a été créé. Je savais aussi que je ne me souciais que des secondes donc c'est simplifié autour de cette idée. Il vous permet également d'annuler et de le redémarrer. Je ne fais pas de pause parce que ce n'est pas dans mes besoins.

/**
* Created by MinceMan on 8/2/2014.
*/
public abstract class SecondCountDownTimer {

private final int seconds;
private TimerThread timer;
private final Handler handler;

/**
 * @param secondsToCountDown Total time in seconds you wish this timer to count down.
 */
public SecondCountDownTimer(int secondsToCountDown) {
    seconds = secondsToCountDown;
    handler = new Handler();
    timer = new TimerThread(secondsToCountDown);
}

/** This will cancel your current timer and start a new one.
 *  This call will override your timer duration only one time. **/
public SecondCountDownTimer start(int secondsToCountDown) {
    if (timer.getState() != State.NEW) {
        timer.interrupt();
        timer = new TimerThread(secondsToCountDown);
    }
    timer.start();
    return this;
}

/** This will cancel your current timer and start a new one. **/
public SecondCountDownTimer start() {
    return start(seconds);
}

public void cancel() {
    if (timer.isAlive()) timer.interrupt();
    timer = new TimerThread(seconds);
}

public abstract void onTick(int secondsUntilFinished);
private Runnable getOnTickRunnable(final int second) {
    return new Runnable() {
        @Override
        public void run() {
            onTick(second);
        }
    };
}

public abstract void onFinish();
private Runnable getFinishedRunnable() {
    return new Runnable() {
        @Override
        public void run() {
            onFinish();
        }
    };
}

private class TimerThread extends Thread {

    private int count;

    private TimerThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        try {
            while (count != 0) {
                handler.post(getOnTickRunnable(count--));
                sleep(1000);
            }
        } catch (InterruptedException e) { }
        if (!isInterrupted()) {
            handler.post(getFinishedRunnable());
        }
    }
}

}

2
répondu MinceMan 2014-08-02 21:43:25

ajouter quelques millisecondes à votre minuterie pour lui donner le temps de traiter le code. J'ai ajouté +100 pour votre minuterie de longueur, et aussi Math.ceil() pour arrondir le résultat, plutôt que d'ajouter 1.

aussi... la première tique est AFTER 2000 millis, donc vous n'obtiendrez pas une entrée" 10 secondes restantes " à moins de l'ajouter.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final TextView tv = (TextView) findViewById(R.id.tv);
    tv.setText("10 Seconds remain\n"); //displayed before the first tick.
    new CountDownTimer(10000+25, 1000) { //25 to account for processing time
        public void onTick(long m) {
            long sec = (long) Math.ceil(m / 2000 ); //round up, don't add 1
            tv.append(sec + " seconds remain\n");
        }
        public void onFinish() {
            tv.append("Done!");
        }
    }.start();
}
2
répondu Benjamin Kershner 2018-05-23 17:37:19

pour développer la réponse de Nantoka. Voici mon code pour vous assurer que la vue est mise à jour correctement:

countDownTimer = new CountDownTimer(countDownMsec, 500) 
{
    public void onTick(long millisUntilFinished)
    {
        if(millisUntilFinished!=countDownMsec)
        {
            completedTick+=1;
            if(completedTick%2==0)      // 1 second has passed
            {
                // UPDATE VIEW HERE based on "seconds = completedTick/2"
            }
            countDownMsec = millisUntilFinished;  // store in case of pause
        }
    }

    public void onFinish()
    {
        countDownMsec = 0;
        completedTick+=2;       // the final 2 ticks arrive together
        countDownTimer = null;

        // FINAL UPDATE TO VIEW HERE based on seconds = completedTick/2 == countDownMsec/1000
    }
}
1
répondu Simon Huckett 2016-11-23 10:26:36

si votre Intervalle de temps est supérieur à 4 secondes alors tous les onTick() appel ne serait pas convenable. Donc si vous voulez un résultat précis, alors gardez l'intervalle de moins de 5 secondes. La recherche est au début de chaque tic, avant onTick() est appelé, le temps restant jusqu'à la fin du compte à rebours est calculé et si ce temps est inférieur à l'intervalle de temps du compte à rebours,onTick() n'a plus appelé. Au lieu de cela, seulement la prochaine tique (où le onFinish() méthode sera appelée) est prévue.

1
répondu Kundan 2017-11-10 15:05:32

j'ai également fait face au même problème avec CountDownTimer et j'ai essayé différentes approches. Ainsi, l'une des solutions les plus faciles est fournie par @Nantoca - il suggère de doubler la fréquence de 1000ms à 500ms. Mais je n'aime pas cette solution parce qu'elle fait plus de travail qui consommera une certaine ressource de batterie supplémentaire.

J'ai donc décidé d'utiliser la suggestion de @ocanal et d'écrire mon propre CustomCountDownTimer.

Mais j'ai trouvé quelques défauts dans son code:

  1. c'est un peu inefficace (créer un second handler pour publier les résultats)

  2. Il commence à publier le premier résultat avec un retard. (Vous avez besoin de faire un post() plutôt que postDelayed() lors de la première initialisation)

  3. bizarre. Méthodes avec la lettre capitale, le statut au lieu de classique isCanceled booléen et un autre.

Donc j'ai nettoyé un peu et voici les plus courantes version de son approche:

private class CustomCountDownTimer {

    private Handler mHandler;
    private long millisUntilFinished;
    private long countDownInterval;
    private boolean isCanceled = false;

    public CustomCountDownTimer(long millisUntilFinished, long countDownInterval) {
        this.millisUntilFinished = millisUntilFinished;
        this.countDownInterval = countDownInterval;
        mHandler = new Handler();
    }

    public synchronized void cancel() {
        isCanceled = true;
        mHandler.removeCallbacksAndMessages(null);
    }

    public long getRemainingTime() {
        return millisUntilFinished;
    }

    public void start() {

        final Runnable counter = new Runnable() {

            public void run() {

                if (isCanceled) {
                    publishUpdate(0);
                } else {

                    //time is out
                    if(millisUntilFinished <= 0){
                        publishUpdate(0);
                        return;
                    }

                    //update UI:
                    publishUpdate(millisUntilFinished);

                    millisUntilFinished -= countDownInterval;
                    mHandler.postDelayed(this, countDownInterval);
                }
            }
        };

        mHandler.post(counter);
    }
}
0
répondu Kirill Karmazin 2017-06-11 18:16:11

vous calculez le temps restant incorrectement. Le rappel obtient le nombre de millisecondes jusqu'à l'achèvement de la tâche.

public void onTick(long m) {  
    long sec = m/1000+1;  
    tv.append(sec+" seconds remain\n");  
}  

doit être

public void onTick(long m) {  
    long sec = m/1000;  
    tv.append(sec+" seconds remain\n");  
}

Je n'ai jamais utilisé cette classe moi-même mais il semble que vous n'obtiendrez pas un rappel au moment où il commence, ce qui est pourquoi il semble que vous manquez une entrée. par exemple, 10000 ms, 1000 ms par Tic vous obtiendriez un total de 9 callbacks de mise à jour, pas 10 - 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, terminer.

-1
répondu I82Much 2012-01-14 04:56:09