Domanda

Codice:

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 remain\n");  
            }  
            public void onFinish() {  
               tv.append("Done!");  
            }  
        }.start();  
   }

Produzione:
Rimangono 10 secondi
Rimangono 8 secondi
Rimangono 6 secondi
Rimangono 4 secondi
Fatto!

Problema:

Come lo faccio a mostrare "Rimangono 2 secondi"? Il tempo trascorso è effettivamente 10 secondi, ma l'ultimo ontick () non si verifica mai. Se cambio il secondo parametro da 2000 a 1000, allora questo è l'output:

Rimangono 10 secondi
Rimangono 9 secondi
Rimangono 8 secondi
Rimangono 7 secondi
Rimangono 6 secondi
Rimangono 5 secondi
Rimangono 4 secondi
Rimangono 3 secondi
Rimangono 2 secondi
Fatto!

Quindi vedi, sembra saltare l'ultima chiamata di Ontick (). E btw, il file XML è fondamentalmente il main.xml predefinito con TextView assegnato l'ID tv e il testo impostato su "".

È stato utile?

Soluzione

Non so perché l'ultimo tick non funzioni ma puoi creare il tuo timer con RUNABLE , Per esempio.

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);
    }
}

E per iniziare,

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

Modifica per la domanda di Goofy

Dovresti avere una variabile per contenere lo stato del contatore (booleano). Quindi puoi scrivere un metodo stop () come start ().

EDIT-2 per la domanda di Goofy

In realtà non esiste un bug sul contatore di arresto, ma c'è un bug all'inizio dopo la fermata (riprendi).

Sto scrivendo un nuovo codice completo aggiornato che avevo appena provato e funziona. È un contatore di base che mostra il tempo sullo schermo con il pulsante Start and Stop.

classe per contatore

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 di attività

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>

Altri suggerimenti

Ho controllato il codice sorgente di CountdownTimer. Il "segno mancante" proviene da una caratteristica speciale di CountdownTimer che non ho ancora visto documentazione altrove:

All'inizio di ogni segno di spunta, prima che venga chiamato ONtick (), il tempo rimanente fino alla fine del conto alla rovescia. Se questa volta è più piccolo dell'intervallo di tempo del conto alla rovescia, Ontick lo è non chiamato più. Invece è previsto solo il prossimo segno di spunta (dove verrà chiamato il metodo OnFinish ()).

Dato il fatto che gli orologi hardware non sono sempre molto precisi, che potrebbero esserci altri processi in background che ritardano il thread in esecuzione di Countdowntimer più che Android stesso creerà probabilmente un piccolo ritardo quando si chiama il gestore dei messaggi di CountdownTimer, è più che probabile La chiamata per l'ultima zecca prima della fine del conto in basso sarà in ritardo di almeno un millisecondo e quindi ONtick () non verrà chiamata.

Per la mia applicazione ho risolto questo problema semplicemente rendendo gli intervalli di tick "leggermente" più piccoli (500 ms)

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

E potrei lasciare il mio codice così com'è. Per le applicazioni in cui la durata del tempo di intervallo è fondamentale, le altre soluzioni pubblicate qui sono probabilmente le migliori.

La soluzione più semplice che mi è venuta in mente è la seguente. Si noti che funziona solo se hai bisogno di una semplice schermata da visualizzare con un conto alla rovescia di secondi.

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

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

        mTimer.start();

Nel codice sopra l'Ontick () viene chiamato ogni 100 millisecondi ma vengono visualizzati visivamente solo secondi.

Ho trascorso ore a cercare di capire questo problema e sono felice di mostrarti un bel lavoro in giro. Non preoccuparti di aspettare il onFinish() Chiama, basta aggiungere 1 (o qualunque sia il tuo intervallo) alle tue unità, quindi aggiungi un'istruzione if in onTick() chiamate. Fai solo il tuo onFinish() tas (i) sull'ultimo onTick(). Ecco cosa ho:

    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();

Mentre la soluzione sopra è valida, può essere ulteriormente migliorata. Ha inutilmente un incontro all'interno di un'altra classe (che può già essere trattata da sola). Quindi basta creare una classe che estende un thread (o eseguibile).

    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);
        }
      }
    }

Ho trovato una soluzione facile. Ho bisogno di conto alla rovescia per aggiornare ProgressBar, quindi l'ho fatto:

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);
    }

};

Piccolo tick fa il trucco :)

Quindi penso di essere andato un po 'a bordo perché il mio timer corre nel suo thread invece di usare i gestori di postdelay, anche se è sempre stato pubblicato sul thread in cui è stato creato. Sapevo anche che mi importava solo di pochi secondi, quindi è semplificato attorno a quello idea. Ti consente anche di annullarlo e riavviarlo. Non ho fatto una pausa incorporata perché non ha i miei bisogni.

/**
* 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());
        }
    }
}

}

Aggiungi alcuni millisecondi al timer per concedere il tempo di elaborare il codice. Ho aggiunto +100 al tuo timer-lunghezza e anche Math.ceil() per arrotondare il risultato, anziché aggiungere 1.

Anche ... il primo segno di spunta è DOPO 2000 millis, quindi non riceverai una voce "10 secondi rimasti" a meno che tu non la aggiunga.

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();
}

Per espandere la risposta di Nantoka. Ecco il mio codice per assicurarsi che la vista venga aggiornata correttamente:

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
    }
}

Se il tuo intervallo di tempo è più di 4 secondi, allora ogni onTick() La chiamata non sarebbe corretta. Quindi, se si desidera un risultato preciso, mantieni l'intervallo inferiore a 5 sec. La ricerca è all'inizio di ogni segno di spunta, prima onTick() viene chiamato, il tempo rimanente fino alla fine del conto alla rovescia viene calcolato e se questa volta è inferiore all'intervallo di tempo del conto alla rovescia, onTick() Non non vorrei più chiamato. Invece solo il successivo segno di spunta (dove il onFinish() Il metodo verrà chiamato) è programmato.

Ho anche affrontato lo stesso problema con CountdownTimer e ho provato approcci diversi. Quindi uno dei modi più semplici è in soluzione fornita da @nanloca - suggerisce di raddoppiare la frequenza da 1000 ms a 500 ms. Ma non mi piace questa soluzione perché fa più lavoro che consumerà una risorsa di batteria in più.

Così ho deciso di usare l'anima di @Ocanal e di scrivere il mio semplice CustomCountDownTimer.

Ma ho trovato un paio di difetti nel suo codice:

  1. È un po 'inefficiente (creando il secondo gestore per pubblicare i risultati)

  2. Inizia a pubblicare il primo risultato con un ritardo. (Devi fare un post() metodo piuttosto che postDelayed() Durante la prima inizializzazione)

  3. dall'aspetto strano. Metodi con la lettera di capitale, lo stato invece del classico booleano iscancato e altri.

Quindi l'ho pulito un po 'ed ecco la versione più comune del suo approccio:

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);
    }
}

Stai calcolando il tempo rimanente in modo errato. Il callback ottiene il numero di millisecondi fino al completamento dell'attività.

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

dovrebbe essere

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

Non ho mai usato questa lezione da solo, ma sembra che non otterrai un callback nell'istante che inizia, motivo per cui sembra che ti manchi una voce. EG 10000 ms, 1000 ms per tick otterresti un totale di 9 callback di aggiornamento, non 10 - 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, finitura.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top