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()从来没有发生过。 如果我将第二个参数从2000更改为1000,那么这是输出:
保持10秒
保持9秒
还剩8秒
保持7秒
还剩6秒
还剩5秒
保持4秒
保持3秒
保持2秒
完成!
所以你看,这似乎是跳过最后onTick()调用。 顺便说一下,XML文件基本上是默认的main.xml,TextView分配了id 电视和文本设置为“”。
我不知道为什么最后一个滴答不工作,但你可以创build自己的定时器与运行 ,例如。
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();
编辑为好奇的问题
你应该有一个variables来保持计数器状态(布尔值)。 那么你可以写一个Stop()方法,像Start()。
编辑2 oft的问题
实际上没有停止计数器的错误,但是在停止(恢复)之后再次启动有一个错误。
我正在写一个新的更新完整的代码,我刚刚尝试,它的工作。 这是一个基本的计数器,显示在屏幕上的开始和停止button的时间。
柜台类
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>
我检查了CountDownTimer的源代码。 CountDownTimer的一个特殊function是“缺less的tick”,我还没有看到其他地方有logging:
在每个滴答的开始,在onTick()被调用之前,计算直到倒数结束的剩余时间。 如果这个时间小于倒数时间间隔,则onTick 不再被调用。 相反,只有下一个勾号(onFinish()方法将被调用)被调度。
鉴于硬件时钟并不总是超精确的,在后台可能会有其他进程延迟线程运行CountDownTimer加上Android本身可能会创build一个小的延迟,当调用CountDownTimer的消息处理程序是很可能的倒计数结束前的最后一个滴答的呼叫将延迟至less一毫秒,因此onTick()将不会被调用。
对于我的应用程序,我简单地通过使滴答间隔“稍微”变小(500毫秒)来解决这个问题,
myCountDownTimer = new CountDownTimer(countDownTime, intervalTime - 500) { ... }
我可以保留我的代码。 对于间隔时间长度很重要的应用,这里发布的其他解决scheme可能是最好的。
我花了好几个小时试图弄清楚这个问题,我很高兴向你展示一个很好的解决方法。 不要等待onFinish()
调用,只需在你的单元中加1(或者你的间隔),然后在onTick()
调用中添加一个if语句。 只需在onFinish()
执行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();
虽然上述解决scheme是有效的,但可以进一步改进。 它不必要地在另一个类中运行(已经可以自己处理)。 因此,只需创build一个扩展线程(或可运行)的类。
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); } } }
我提出的最简单的解决scheme如下。 请注意,只有当您需要简单的屏幕才能显示秒倒计时时,它才有效。
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毫秒被调用一次,但是只能看到秒数。
我发现简单的解决scheme 我需要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); } };
小勾子做的诀窍:)
所以我觉得我有点过度了,因为我的计时器在自己的线程中运行,而不是使用postDelay处理程序,尽pipe它总是回到它创build的线程。我也知道,我只关心秒,所以它简化了理念。 它也可以让你取消它并重新启动它。 我没有暂停内置,因为这不符合我的需求。
/** * 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()); } } }
}
展开南托卡的答案。 这里是我的代码,以确保正确更新视图:
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 } }
你正在计算剩余的时间不正确。 callback获得毫秒数,直到完成任务。
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"); }
我从来没有使用过这个类,但是看起来你不会在开始的时候得到一个callback,这就是为什么它看起来像你缺less一个条目。 例如10000毫秒,每个滴答1000毫秒,你会得到总共9个更新callback,而不是10 – 9000,8000,7000,6000,5000,4000,3000,2000,1000,完成。
我也遇到了与CountDownTimer相同的问题,我尝试了不同的方法。 所以最简单的方法之一是@Nantoca提供的解决scheme – 他build议将频率从1000ms加倍到500ms。 但是我不喜欢这个解决scheme,因为它会使更多的工作消耗额外的电池资源。
所以我决定使用@ ocanal的灵魂,并写我自己的简单的CustomCountDownTimer。
但是我发现他的代码中有一些缺陷:
-
这有点低效(创build第二个处理程序来发布结果)
-
它开始迟迟地发布第一个结果。 (您需要在第一次初始化时执行
post()
方法而不是postDelayed()
) -
奇怪的样子。 大写字母,状态而不是经典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); } }
如果您的时间间隔超过4秒,则每次onTick()
调用都不正确。 所以如果你想得到精确的结果,那么保持间隔小于5秒。 Reseaon是在每个tick的开始,在onTick()
被调用之前,剩余的时间直到倒计时结束,如果这个时间小于倒计时的时间间隔, onTick()
将不会再被调用。 相反,只有下一个勾号( onFinish()
方法将被调用)被调度。