2012-01-13 3 views
37

Код:Android: CountDownTimer пропускает последний onTick()!

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

Выход:
10 секунд остаются
секунд остаются
6 секунд остаются
4 секунды остаются
Готово!

Проблема:

Как я могу получить его, чтобы показать "2 секунды остаются"? Прошло время действительно 10 секунд, но последний onTick() никогда не происходит. Если изменить второй параметр от 2000 до 1000, то это выход:

10 секунд остаются
9 секунд остаются
секунд остаются
7 секунд остаются
6 секунд остаются
5 секунд остаются
Осталось: 4 секунды
Осталось: 3 секунды:
Осталось: 2 секунды
Done!

Итак, вы видите, что это пропускает последний вызов onTick(). И, кстати, XML-файл в основном является основным. Main.xml с TextView назначил id tv и текст установлен на "".

ответ

21

Я не знаю, почему последний тик не работает, но вы можете создать свой собственный таймер с Runable, например.

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

и запустить его,

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

редактирования.К Гуфи ВОПРОС

вы должны иметь переменную для хранения состояния счетчика (логическое). то вы можете написать метод Stop(), например Start().

EDIT-2 ДЛЯ Гуфи ВОПРОС

на самом деле не существует ошибка при остановке счетчика, но есть ошибка на старте снова после остановки (резюме).

Я пишу новый обновленный полный код, который я только что попробовал, и он работает. Это базовый счетчик, показывающий время на экране с кнопкой запуска и остановки.

класс для счетчика

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

класс активности

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> 
+1

Я полагал, что я должен просто использовать обработчик, чтобы заботиться о нем, но минус недостаток я говорил в моем вопросе, то CountDownTimer так просто и идеально! Я имею в виду только из вашего примера кода и моего, вы можете видеть, как простой и компактный CountDownTimer сравнивается с обработчиком! Если бы это действительно работало правильно, хотя LOL. – ProgrammingMakesMeQQ

+3

абсолютно вы правы, я просто хочу показать, что вы можете сделать это с помощью альтернативных методов. – ocanal

+0

@ocanal спасибо, я использую ваш код, но если я хочу остановить таймер, как это сделать ... ответьте – Goofy

0

Вы Расчитать время ожидания остается неправильным. Обратный вызов получает количество миллисекунд до завершения задачи.

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

должен быть

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

Я никогда не использовал этот класс сам, но, похоже, вы не получите обратный вызов в тот момент, она начинается, поэтому он выглядит как вы теряете запись. например 10000 мс, 1000 мс за галочку, вы получите в общей сложности 9 обратных вызовов обновления, а не 10 - 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, финиш.

+2

Я сделал это +1, чтобы сделать вывод понятным. Результат, который у меня есть в моем вопросе, отражает фактическое время, оставшееся до окончания обратного отсчета, например. когда он говорит «3 секунды ... 2 секунды ... Готово!» это означает, что на самом деле осталось 2 секунды до того, как «Готово!» появилось сообщение. Поэтому в основном, если я вытащу «+1» из «long sec = m/1000», тогда текст скажет «2 секунды ... 1 секунда ... Сделано!» Несмотря на то, что когда он говорит «1 секунда», есть действительно 2 секунды. Это звучит немного запутанно, но если вы попробуете мой короткий код, вы увидите, о чем я говорю. – ProgrammingMakesMeQQ

3

Я потратил часы, пытаясь понять эту проблему, и я рад показать вам хорошую работу. Не беспокойтесь, ожидая вызова onFinish(), просто добавьте 1 (или какой бы то ни было ваш интервал) к вашим устройствам, а затем добавьте оператор if в вызовы onTick(). Просто выполните свои onFinish() задачи (ы) на последних onTick(). Вот что у меня есть:

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

Мне нравится решение, но я нахожу, что, по крайней мере, для Android> = 6 проблема заключается в том, что окончательный onTick() не получает вызов, но onFinish() по-прежнему вызывает вызов в нужное время. Поскольку я только обновляю дисплей обратного отсчета в своем onTick, я просто использую ваше решение, чтобы показать это окончательное обновление, но оставьте анимацию onFinish() в том же месте. – rockhammer

3

Несмотря на то, что решение выше, оно может быть дополнительно улучшено. Он излишне имеет 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); 
     } 
     } 
    } 
2

Самое простое решение, с которым я столкнулся, заключается в следующем. Обратите внимание, что он работает только в том случае, если вам нужен простой экран для отображения с обратным отсчетом секунд.

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

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

     mTimer.start(); 

В приведенном выше коде onTick() вызывается каждые 100 миллисекунд, но визуально отображаются только секунды.

42

Я проверил исходный код CountDownTimer. «Отсутствующий тик» исходит из особой функции CountDownTimer, которую я еще не видел, зарегистрированной в другом месте:

В начале каждого тика до вызова onTick() оставшееся время до конца обратного отсчета рассчитывается. Если это время меньше, чем время обратного отсчета, onTick - это , а не. Вместо этого запланирован только следующий тик (где будет вызываться метод onFinish()).

С учетом того факта, что аппаратные часы не всегда супер точные, что могут быть другие процессы в фоновом режиме, которые задерживают поток, запущенный CountDownTimer, плюс сам Android, вероятно, создаст небольшую задержку при вызове обработчика сообщений CountDownTimer. более вероятно, что вызов последнего тика до конца отсчета будет как минимум на одну миллисекунду позже, и поэтому onTick() не будет вызван.

Для моего приложения я решил эту проблему, просто сделав интервалы клеща «слегка» меньше (500 мс)

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

и я мог бы оставить свой код так же, как это. Для приложений, где длина интервала времени критическая, другие решения, размещенные здесь, вероятно, являются лучшими.

+1

Да, вы также можете сказать «countDownTime + 900» или что-то в этом роде , С обратным отсчетом 10000 и интервалом в 1000 вы увидите только 10 тиков, если каждому тику удается точно стрелять вовремя. Добавление 900 мс в этом примере не даст ему достаточно времени для еще 1 тика, но даст ему достаточно времени для «последнего» тика с некоторым запасом для того, чтобы часы не были точно по расписанию. (например, когда последний тик начнется с «10004» мс от начала, вы пропустите его, но если вы добавите небольшое заполнение, все будет хорошо) – JamieB

+0

Изменение галочки с 1000 мс до 500 мс исправило это и для меня. – rsa

+0

Это работает для меня! Большое спасибо!! –

2

Я нашел легкое решение. Мне нужно CountDown обновить ProgressBar, так что я сделал это:

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

}; 

Малый тик сделать трюк :)

+0

работает, спасибо –

2

Так что я думаю, что я пошел немного за борт, потому что мой таймер работает в своем собственном потоке, вместо того, чтобы использовать postDelay, хотя он всегда возвращается к потоку, в котором он был создан. Я также знал, что я только заботился о секундах, чтобы упростить эту идею. Он также позволяет вам отменить его и перезапустить. У меня нет паузы, потому что это не в моих потребностях.

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

}

1

Чтобы расширить ответ Nantoka в. Вот мой код, чтобы обеспечить представление правильно обновлением:

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

я также столкнулся с теми же проблемами с CountDownTimer и я пробовал разные подходы. Итак, один из самых простых способов - это решение, предоставленное @Nantoca - он предлагает удвоить частоту от 1000 мс до 500 мс. Но мне не нравится это решение, потому что он делает больше работы, которая будет потреблять дополнительный ресурс батареи.

Итак, я решил использовать душу @ ocanal и написать собственный простой CustomCountDownTimer.

Но я нашел пару недостатков в его коде:

  1. Это немного неэффективно (создание второго обработчика для публикации результатов)

  2. Он начинает издавать первый результат с задержкой. (Вам нужно сделать post() метод, а не postDelayed() во время первой инициализации)

    нечетный вид. Методы с заглавной буквы, статус вместо классического isCanceled boolean и некоторые другие.

Так я очистил его немного, и вот более общий вариант его подхода:

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

, если Ваше время интервал более 4 сек, то каждый onTick() вызов не был бы правильным. Поэтому, если вы хотите получить точный результат, продолжайте интервал менее 5 секунд. Reseaon находится в начале каждого тика до того, как вызывается onTick(), оставшееся время до конца отсчета рассчитывается, и если это время меньше, чем время обратного отсчета, onTick() больше не будет вызываться. Вместо этого запланирован только следующий тик (где будет вызываться метод onFinish()).

Смежные вопросы