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

相关问题