2012-01-13 142 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秒保持
8秒保持
6秒保持
4秒保持
完成!

問題:

我如何得到它顯示 「2秒保持」?經過的時間確實是10秒,但最後的onTick()從不發生。如果我改變1000至00年的第二個參數,則這是輸出:

10秒保持
9秒保持
8秒保持
7秒保持
6秒保持
5秒保持
保持4秒
保持3秒
保持2秒
完成!

所以你看,它似乎在跳過最後一次onTick()調用。順便說一句,XML文件基本上是默認的main.xml,TextView分配了ID 電視,文本設置爲「」。

回答

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與Handler相比簡單而緊湊!如果只是它實際上正常工作,雖然哈哈。 – ProgrammingMakesMeQQ 2012-01-15 23:18:23

+3

絕對你是對的,我只是想表明你可以用其他方法做到這一點。 – ocanal 2012-01-16 00:46:29

+0

@ocanal謝謝我使用你的代碼,但如果我想停止計時器如何做...請回復 – Goofy 2012-03-26 10:25:09

0

你是calcula時間不正確。回調獲得毫秒數,直到完成任務。

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 ms,每個tick有1000 ms,您將獲得總共9次更新回調,而不是10 - 9000,8000,7000,6000,5000,4000,3000,2000,1000。

+2

我做了+1以使輸出有意義。我在我的問題中的輸出反映了直到倒計數結束爲止的實際時間。當它說「3秒... 2秒...完成!」這意味着在「完成!」之前還剩2秒。消息出現了。所以基本上,如果我從「long sec = m/1000」中取出「+1」,那麼文本將會顯示「2秒... 1秒...完成!」儘管當它說「1秒」時,真的有2秒鐘的時間。這聽起來有點令人困惑,但如果你嘗試了我的短代碼,你會看到我在說什麼。 – ProgrammingMakesMeQQ 2012-01-15 23:14:30

3

我花了好幾個小時試圖找出這個問題,我很高興爲您展示一個很好的解決方法。不要打擾等待onFinish()呼叫,只需向您的單位加1(或任何間隔時間),然後在onTick()呼叫中添加if語句。只需在最近的onTick()上執行onFinish()任務。這是我得到的:

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 2016-10-19 01:19:01

3

雖然上面的解決方案是有效的,但可以進一步改進。它不必要地在另一個類中運行(它可以自己處理)。所以只需創建一個擴展線程(或可運行)的類。

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

在上面的代碼中,每100毫秒調用一次onTick(),但在視覺上只顯示秒。

42

我檢查了CountDownTimer的源代碼。 「丟失的滴答聲」來自CountDownTimer的一個特殊功能,我還沒有看到其他地方有記錄:

在每個滴答的開始,在onTick()被調用之前,直到倒數結束的剩餘時間是計算。如果此時間小於倒計時時間間隔,onTick將不會再調用而是調用。相反,只調度下一個勾號(onFinish()方法將被調用)。

鑑於硬件時鐘並不總是超精確的事實,在後臺可能有其他進程延遲線程運行CountDownTimer加上Android本身可能會創建一個小的延遲時調用CountDownTimer的消息處理程序它是倒計數結束前的最後一個滴答聲的呼叫很可能至少延遲一毫秒,因此onTick()將不會被調用。

對於我的應用程序解決了這個問題,簡單地通過使刻度間隔「稍微」小(500毫秒)

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

,我可以離開我的代碼只是因爲它是。對於間隔時間長度很重要的應用,這裏發佈的其他解決方案可能是最好的。

+1

是的,或者您可以說「countDownTime + 900」或類似的東西。倒數計時間爲10000,間隔時間爲1000,如果每個時間點能夠準確按時發射,則只能看到10個時間點。在這個例子中增加900ms並不足以讓它有足夠的時間再增加1次滴答,但是會給它足夠的時間用於「最後」滴答,並且時鐘的某些餘量不能精確按計劃執行。 (例如,當最後一次打勾從開始打到「10004」ms時,你會錯過它,但是如果你添加了一點填充,那就沒問題了) – JamieB 2012-10-25 18:37:57

+0

將打勾從1000ms改爲500ms也爲我修正了這個問題。 – rsa 2012-10-27 19:36:55

+0

這適用於我!非常感謝!! – 2013-11-23 22:59:57

2

我找到了簡單的解決方案。我需要倒計時更新進度,所以我這樣做:

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

作品,謝謝 – 2016-09-23 11:32:36

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提供的解決方案 - 他建議將頻率從1000ms加倍到500ms。但是我不喜歡這個解決方案,因爲它使得更多的工作會消耗一些額外的電池資源。

所以我決定使用@ ocanal的靈魂,並寫我自己的簡單的CustomCountDownTimer。

但我發現夫婦在他的代碼缺陷:

  1. 這有點低效(創建第二個處理器發佈結果)

  2. 它開始發佈第一個結果有延遲。 (您需要在第一次初始化期間執行post()方法而不是postDelayed()

  3. 奇怪的看。大寫字母,狀態而不是經典的方法isCanceled布爾值等。

所以我把它清理了一下,這裏是他的做法比較常見的版本:

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()方法)被安排。