CountDownTimer 在秒之间闪烁
CountDownTimer flickers between seconds
我有一个 CountDownTimer,大多数时候都运行良好。但是,它有时会在显示倒计时的 TextView 中开始闪烁。我把它显示成这样:
HH:MM:SS
在倒计时的过程中,最后一秒会在一秒内从 9 跳到 7 再到 8。然后下一秒就会从8快速闪烁到6再到7
我尝试将变量 millisUntilFinished 直接传递给更新 textView 的方法,但问题仍然存在。请注意,我正在保存倒计时并在 onStop 和 onStart 方法期间继续它。
private fun startVisibleCountdown() {
visibleCountdownRunning = true
object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {
override fun onTick(millisUntilFinished: Long) {
timeLeftInMillisecondsVisibleCounter = millisUntilFinished
updateCountDownTextVisible()
}
override fun onFinish() {
//not relevant here
}
}.start()
}
fun updateCountDownTextVisible() {
var seconds = (timeLeftInMillisecondsVisibleCounter / 1000).toInt()
val hours = seconds / (60 * 60)
val tempMint = seconds - hours * 60 * 60
val minutes = tempMint / 60
seconds = tempMint - (minutes * 60);
textViewTimer.text = (String.format("%02d", hours)
+ ":" + String.format("%02d", minutes)
+ ":" + String.format("%02d", seconds))
}
应用关闭时保存并返回倒计时:
override fun onStop() {
super.onStop()
if (visibleCountdownRunning) {
timeLeftInMillisecondsVisibleCounter += System.currentTimeMillis()
}
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
with (sharedPref.edit()) {
putLong(timeLeftVisibleCounterKey, timeLeftInMillisecondsVisibleCounter)
putBoolean(visibleCountdownRunningKey, visibleCountdownRunning)
apply()
}
}
override fun onStart() {
super.onStart()
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
timeLeftInMillisecondsVisibleCounter = sharedPref.getLong(timeLeftVisibleCounterKey, 43200000)
visibleCountdownRunning = sharedPref.getBoolean(visibleCountdownRunningKey, false)
if (visibleCountdownRunning) {
timeLeftInMillisecondsVisibleCounter -= System.currentTimeMillis()
if (timeLeftInMillisecondsVisibleCounter > 0) {
startVisibleCountdown()
} else {
timeLeftInMillisecondsVisibleCounter = 43200000
visibleCountdownRunning = false
}
}
}
试试这个,它适用于我,但它 java,将其转换为 Kotlin,它会适用于你
long time = 20 *60;
new CountDownTimer( time * 1000, 1000) {
@SuppressLint("SetTextI18n")
public void onTick(long millisUntilFinished) {
String timeremaining = TimetoString(millisUntilFinished);
// update your textview here
}
public void onFinish() {
// here when the counter finish
}
}.start();
由于您是从 onStart()
和单击按钮时调用 startVisibleCountdown()
函数,因此您可能有 2 个 CountDownTimer
对象 运行 并调用您的 updateCountDownTextVisible()
代码同时。这可以用一种奇怪的方式解释闪烁和跳秒。
请记住,即使您在 onStart()
函数中注释掉对 startVisibleCountdown()
的调用,Android 有时也不会阻止多次点击(取决于什么您正在使用的按钮类型以及您是如何连接 onClickListener 的),因此您可能还会在单击按钮时启动多个倒数计时器。
在你startVisibleCountdown()
中添加一些日志输出(Log.d(...)
),只是为了确保它不会被多次调用。
编辑:由于您检查过您的点击处理程序没有被多次调用,这意味着当您的 activity/fragment 生命周期停止并重新启动时,您不会在代码中的任何地方停止倒计时(我们只能看到你的 onStop 和 onStart 方法,但你不会在那里停止倒计时)。
这意味着即使在调用 onStop 之后,计时器也会在后台保持 运行。因此,当再次调用 onStart 时,它将启动另一个计时器,并且根据时间(这就是它有点随机的原因),您可能会看到秒数“跳跃”然后再次“跳跃”。
所以我的建议还是和以前一样。
在您的 startVisibleCountdown()
中添加一些日志输出,只是为了确保它不会被多次调用。
重现闪烁行为并观察日志。它将在其中多次调用 startVisibleCountdown()
。
您可以只开始倒计时,将应用程序置于后台,稍等片刻,然后重新打开。如果第一次不行就重复几次,因为这取决于时间。
要解决此问题,请在 onStop() 方法中停止倒数计时器。
// to keep the reference to the CountDownTimer
private var countDown: CountDownTimer? = null
override fun onStop() {
// ...
countDown?.cancel()
countDown = null
// ...
}
private fun startVisibleCountdown() {
//...
countDown = object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {
// ...
}
countDown.start()
}
每次 onStart
触发(例如,通过转到主屏幕然后返回到应用程序)如果当前计时器还有剩余时间,您将通过该 startVisibleCountdown()
调用启动另一个计时器。你还没有表明你在任何地方停止旧的 - 你的代码甚至没有保留对它的引用,它只是匿名启动,并运行直到它完成(或应用程序被杀死)
如果您有多个计时器 运行,它们都会根据传递给它们 onTick
的值设置 timeLeft
,然后调用显示该值的函数。由于它们的更新都被发布到消息队列中,它们之间可能存在轻微的时间差异(例如,一个有 millisUntilFinished = 6000
,一个有 5999
),你把它们打乱了。
这可以解释为什么它会发生变化(你有多个计时器在 TextView
上设置文本,而实际上应该只有一个)以及为什么它会倒退(没有硬性保证哪个消息在排在队列前面,甚至在它刚好到达时)
所以你需要确保你只有 运行 你的计时器的一个实例 - 有几种方法可以处理这个问题,这可能是最安全的方法(不是线程安全的,但那不是你在这里做的事情有问题):
private var myTimer: CountdownTimer? = null
...
fun startVisibleCountdown() {
// enforce a single current timer instance
if (myTimer != null) return
myTimer = object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {
...
override fun onFinish() {
// if a timer finishes (i.e. it isn't cancelled) clear it so another can be started
myTimer = null
}
}
}
...
override fun onStop() {
...
// probably better in a function called stopVisibleCountdown() for symmetry and centralising logic
myTimer?.run { cancel() }
myTimer = null
}
这样一来,您实际上只在一个地方创建并启动计时器,并且该代码确保一次只有一个实例 运行。该实例仅在计时器成功完成或明确停止时被清除。通过像这样集中它(并将所有防御性代码放在一个地方),您可以从多个地方(onStart
、某些按钮的 onClick
)调用启动函数,并且它都将被安全处理
我有一个 CountDownTimer,大多数时候都运行良好。但是,它有时会在显示倒计时的 TextView 中开始闪烁。我把它显示成这样:
HH:MM:SS
在倒计时的过程中,最后一秒会在一秒内从 9 跳到 7 再到 8。然后下一秒就会从8快速闪烁到6再到7
我尝试将变量 millisUntilFinished 直接传递给更新 textView 的方法,但问题仍然存在。请注意,我正在保存倒计时并在 onStop 和 onStart 方法期间继续它。
private fun startVisibleCountdown() {
visibleCountdownRunning = true
object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {
override fun onTick(millisUntilFinished: Long) {
timeLeftInMillisecondsVisibleCounter = millisUntilFinished
updateCountDownTextVisible()
}
override fun onFinish() {
//not relevant here
}
}.start()
}
fun updateCountDownTextVisible() {
var seconds = (timeLeftInMillisecondsVisibleCounter / 1000).toInt()
val hours = seconds / (60 * 60)
val tempMint = seconds - hours * 60 * 60
val minutes = tempMint / 60
seconds = tempMint - (minutes * 60);
textViewTimer.text = (String.format("%02d", hours)
+ ":" + String.format("%02d", minutes)
+ ":" + String.format("%02d", seconds))
}
应用关闭时保存并返回倒计时:
override fun onStop() {
super.onStop()
if (visibleCountdownRunning) {
timeLeftInMillisecondsVisibleCounter += System.currentTimeMillis()
}
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
with (sharedPref.edit()) {
putLong(timeLeftVisibleCounterKey, timeLeftInMillisecondsVisibleCounter)
putBoolean(visibleCountdownRunningKey, visibleCountdownRunning)
apply()
}
}
override fun onStart() {
super.onStart()
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
timeLeftInMillisecondsVisibleCounter = sharedPref.getLong(timeLeftVisibleCounterKey, 43200000)
visibleCountdownRunning = sharedPref.getBoolean(visibleCountdownRunningKey, false)
if (visibleCountdownRunning) {
timeLeftInMillisecondsVisibleCounter -= System.currentTimeMillis()
if (timeLeftInMillisecondsVisibleCounter > 0) {
startVisibleCountdown()
} else {
timeLeftInMillisecondsVisibleCounter = 43200000
visibleCountdownRunning = false
}
}
}
试试这个,它适用于我,但它 java,将其转换为 Kotlin,它会适用于你
long time = 20 *60;
new CountDownTimer( time * 1000, 1000) {
@SuppressLint("SetTextI18n")
public void onTick(long millisUntilFinished) {
String timeremaining = TimetoString(millisUntilFinished);
// update your textview here
}
public void onFinish() {
// here when the counter finish
}
}.start();
由于您是从 onStart()
和单击按钮时调用 startVisibleCountdown()
函数,因此您可能有 2 个 CountDownTimer
对象 运行 并调用您的 updateCountDownTextVisible()
代码同时。这可以用一种奇怪的方式解释闪烁和跳秒。
请记住,即使您在 onStart()
函数中注释掉对 startVisibleCountdown()
的调用,Android 有时也不会阻止多次点击(取决于什么您正在使用的按钮类型以及您是如何连接 onClickListener 的),因此您可能还会在单击按钮时启动多个倒数计时器。
在你startVisibleCountdown()
中添加一些日志输出(Log.d(...)
),只是为了确保它不会被多次调用。
编辑:由于您检查过您的点击处理程序没有被多次调用,这意味着当您的 activity/fragment 生命周期停止并重新启动时,您不会在代码中的任何地方停止倒计时(我们只能看到你的 onStop 和 onStart 方法,但你不会在那里停止倒计时)。
这意味着即使在调用 onStop 之后,计时器也会在后台保持 运行。因此,当再次调用 onStart 时,它将启动另一个计时器,并且根据时间(这就是它有点随机的原因),您可能会看到秒数“跳跃”然后再次“跳跃”。
所以我的建议还是和以前一样。
在您的 startVisibleCountdown()
中添加一些日志输出,只是为了确保它不会被多次调用。
重现闪烁行为并观察日志。它将在其中多次调用 startVisibleCountdown()
。
您可以只开始倒计时,将应用程序置于后台,稍等片刻,然后重新打开。如果第一次不行就重复几次,因为这取决于时间。
要解决此问题,请在 onStop() 方法中停止倒数计时器。
// to keep the reference to the CountDownTimer
private var countDown: CountDownTimer? = null
override fun onStop() {
// ...
countDown?.cancel()
countDown = null
// ...
}
private fun startVisibleCountdown() {
//...
countDown = object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {
// ...
}
countDown.start()
}
每次 onStart
触发(例如,通过转到主屏幕然后返回到应用程序)如果当前计时器还有剩余时间,您将通过该 startVisibleCountdown()
调用启动另一个计时器。你还没有表明你在任何地方停止旧的 - 你的代码甚至没有保留对它的引用,它只是匿名启动,并运行直到它完成(或应用程序被杀死)
如果您有多个计时器 运行,它们都会根据传递给它们 onTick
的值设置 timeLeft
,然后调用显示该值的函数。由于它们的更新都被发布到消息队列中,它们之间可能存在轻微的时间差异(例如,一个有 millisUntilFinished = 6000
,一个有 5999
),你把它们打乱了。
这可以解释为什么它会发生变化(你有多个计时器在 TextView
上设置文本,而实际上应该只有一个)以及为什么它会倒退(没有硬性保证哪个消息在排在队列前面,甚至在它刚好到达时)
所以你需要确保你只有 运行 你的计时器的一个实例 - 有几种方法可以处理这个问题,这可能是最安全的方法(不是线程安全的,但那不是你在这里做的事情有问题):
private var myTimer: CountdownTimer? = null
...
fun startVisibleCountdown() {
// enforce a single current timer instance
if (myTimer != null) return
myTimer = object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {
...
override fun onFinish() {
// if a timer finishes (i.e. it isn't cancelled) clear it so another can be started
myTimer = null
}
}
}
...
override fun onStop() {
...
// probably better in a function called stopVisibleCountdown() for symmetry and centralising logic
myTimer?.run { cancel() }
myTimer = null
}
这样一来,您实际上只在一个地方创建并启动计时器,并且该代码确保一次只有一个实例 运行。该实例仅在计时器成功完成或明确停止时被清除。通过像这样集中它(并将所有防御性代码放在一个地方),您可以从多个地方(onStart
、某些按钮的 onClick
)调用启动函数,并且它都将被安全处理