【问题标题】:How to create a simple countdown timer in Kotlin?如何在 Kotlin 中创建一个简单的倒数计时器?
【发布时间】:2019-06-03 09:16:10
【问题描述】:

我知道如何在 Java 中创建一个简单的倒数计时器。但我想在 Kotlin 中创建这个。

package android.os;

new CountDownTimer(20000, 1000) {
    public void onTick(long millisUntilFinished) {
        mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
    }
    public void onFinish() {
        mTextField.setText("Time's finished!");
    }
}.start();

我怎样才能使用 Kotlin 做到这一点?

【问题讨论】:

  • 倒数计时器易于使用,但并不十分准确。如果输入时间足够长,您将能够看到计时器随着时间的流逝而跳过几秒钟。系统中的任何延迟都会导致每个滴答声出现毫秒级的延迟,并且随着延迟的累积最终会导致跳过。如需更准确的计时器实现,请查看this post

标签: android kotlin countdowntimer


【解决方案1】:

您可以使用 Kotlin 对象:

val timer = object: CountDownTimer(20000, 1000) {
    override fun onTick(millisUntilFinished: Long) {...}

    override fun onFinish() {...}
}
timer.start()

【讨论】:

  • 在 Kotlin 中使用计时器会泄漏本机内存
  • @AkashChaudhary 我认为 android 建议改用这种方法。我想我在一个 codelab 中看到了它。
【解决方案2】:

我已经解决了 Kotlin 中的计时器问题,如下所示:

class Timer {

    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.Default + job)

    private fun startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: () -> Unit) = scope.launch(Dispatchers.IO) {
        delay(delayMillis)
        if (repeatMillis > 0) {
            while (true) {
                action()
                delay(repeatMillis)
            }
        } else {
            action()
        }
    }

    private val timer: Job = startCoroutineTimer(delayMillis = 0, repeatMillis = 20000) {
        Log.d(TAG, "Background - tick")
        doSomethingBackground()
        scope.launch(Dispatchers.Main) {
            Log.d(TAG, "Main thread - tick")
            doSomethingMainThread()
        }
    }

    fun startTimer() {
        timer.start()
    }

    fun cancelTimer() {
        timer.cancel()
    }
//...
}

我使用协程作为计时器。

【讨论】:

  • 这应该是公认的答案,对于将在哪个调度程序上进行工作有可靠的控制。
  • 应用崩溃时会发生什么?
【解决方案3】:

计时器可以设置为倒计时,在我看来这是最简单的方法。

在您的布局 xml 中添加 Chronometer 视图,示例

<Chronometer  
 android:id="@+id/view_timer"   
 tools:targetApi="24"  
 android:layout_width="wrap_content"  
 android:layout_height="wrap_content"/>

然后在您的活动或片段中:

   view_timer.isCountDown = true
   view_timer.base = SystemClock.elapsedRealtime() + 20000
   view_timer.start()

【讨论】:

  • isCountDown 方法的最低 API 级别为 24(Android 7),因此并非适用于每个项目
  • 那么如何将计数器重置为零?
【解决方案4】:

如果你想用天时分秒来显示倒计时

private lateinit var countDownTimer:CountDownTimer
.
.
.
    fun printDifferenceDateForHours() {

            val currentTime = Calendar.getInstance().time
            val endDateDay = "03/02/2020 21:00:00"
            val format1 = SimpleDateFormat("dd/MM/yyyy hh:mm:ss",Locale.getDefault())
            val endDate = format1.parse(endDateDay)

            //milliseconds
            var different = endDate.time - currentTime.time
            countDownTimer = object : CountDownTimer(different, 1000) {

                override fun onTick(millisUntilFinished: Long) {
                    var diff = millisUntilFinished
                    val secondsInMilli: Long = 1000
                    val minutesInMilli = secondsInMilli * 60
                    val hoursInMilli = minutesInMilli * 60
                    val daysInMilli = hoursInMilli * 24

                    val elapsedDays = diff / daysInMilli
                    diff %= daysInMilli

                    val elapsedHours = diff / hoursInMilli
                    diff %= hoursInMilli

                    val elapsedMinutes = diff / minutesInMilli
                    diff %= minutesInMilli

                    val elapsedSeconds = diff / secondsInMilli

                    txt_timeleft.text = "$elapsedDays days $elapsedHours hs $elapsedMinutes min $elapsedSeconds sec"
                }

                override fun onFinish() {
                    txt_timeleft.text = "done!"
                }
            }.start()
        }

如果您正在导航到另一个活动/片段,请确保取消倒计时

countDownTimer.cancel()

代码输出

51 天 17 小时 56 分 5 秒

【讨论】:

  • 请不要教年轻人使用早已过时且臭名昭著的SimpleDateFormat类。至少不是第一选择。而且不是没有任何保留。今天我们在java.time, the modern Java date and time API, 和它的DateTimeFormatter 中有很多更好的地方。是的,您可以在 Android 上使用它。对于较旧的 Android,请参阅 How to use ThreeTenABP in Android Project
  • DateTimeFormatter 需要最低 api lvl 26 (android 8),使用 SimpleDateFormat 我们可以从 api 21 使用它来处理更多设备,我还将更新答案以使用 DateTimeFormatter :)
  • 有一个反向移植。要将 java.time 与最低 SDK 21 一起使用(例如),请将 ThreeTenABP 添加到您的 Android 项目中。并确保从带有子包的org.threeten.bp 包中导入日期和时间类。在我之前评论末尾的链接中阅读更多内容。
  • @GastónSaillén 我有一个不同的用例,我在跟踪剩余时间。那么在导航到其他片段时是否仍然需要取消计数器......如果是!为什么?
【解决方案5】:

尝试使用对象,像这样:

var countDownTimer = object : CountDownTimer(2000, 1000) {
    // override object functions here, do it quicker by setting cursor on object, then type alt + enter ; implement members
}

试试这个网站:https://try.kotlinlang.org/#/Kotlin%20Koans/Introduction/Java%20to%20Kotlin%20conversion/Task.kt

右上角有一个小按钮“从 Java 转换”,可能对您有用。

编辑:

不要忘记在需要时启动此对象,方法是在声明末尾或活动/片段中的任何位置添加.start()

countDownTimer.start()

【讨论】:

    【解决方案6】:
    class CustomCountDownTimer(var mutableLiveData: MutableLiveData<String>) {
    
        lateinit var timer: CountDownTimer
        val zone = ZoneId.systemDefault()
        val startDateTime: ZonedDateTime = LocalDateTime.now().atZone(zone)
    
        fun start(endOn: Long) {
            if (this::timer.isInitialized) {
                return
            }
            timer = object : CountDownTimer(endOn * 1000, 1000) {
    
                override fun onTick(millisUntilFinished: Long) {
    
                    val stringBuilder = StringBuilder()
    
                    val endDateTime: ZonedDateTime =
                        Instant.ofEpochMilli(millisUntilFinished).atZone(ZoneId.systemDefault())
                            .toLocalDateTime().atZone(zone)
    
                    var diff: Duration = Duration.between(startDateTime, endDateTime)
    
    
    
                    if (diff.isZero() || diff.isNegative) {
                        stringBuilder.append("Already ended!")
                    } else {
                        val days: Long = diff.toDays()
    
                        if (days != 0L) {
                            stringBuilder.append("${days}day : ")
                            diff = diff.minusDays(days)
                        }
    
                        val hours: Long = diff.toHours()
                        stringBuilder.append("${hours}hr : ")
                        diff = diff.minusHours(hours)
    
                        val minutes: Long = diff.toMinutes()
                        stringBuilder.append("${minutes}min : ")
                        diff = diff.minusMinutes(minutes)
    
                        val seconds: Long = diff.getSeconds()
    
                        stringBuilder.append("${seconds}sec")
    
                    }
    
                    mutableLiveData.postValue(stringBuilder.toString())
                    //Log.d("CustomCountDownTimer", stringBuilder.toString())
                }
    
                override fun onFinish() {
                }
            }
    
            timer.start()
        }
    
    
        fun getTimerState(): LiveData<String> {
            return mutableLiveData
        }
    }
    

    使用方法:

    val liveData: MutableLiveData<String> = MutableLiveData()
    val customCountDownTimer = CustomCountDownTimer(liveData)
    customCountDownTimer.start(1631638786) //Epoch timestamp
    customCountDownTimer.mutableLiveData.observe(this, Observer { counterState ->
                counterState?.let {
                    println(counterState)
                }
            })
    

    输出:

    22hr : 42min : 51sec //剩余不到4小时时

    1day : 23hr : 52min : 44sec // 在其他情况下

    【讨论】:

      【解决方案7】:

      Chronometer用于minapi=24

        <Chronometer
                  android:id="@+id/timer_expire_time"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_margin="@dimen/spacing_m"
                  android:countDown="true"
                  android:textColor="@color/white"
                  android:textSize="@dimen/text_size_huge"
                  app:layout_constraintBottom_toBottomOf="parent"
                  app:layout_constraintStart_toStartOf="parent"
                  tools:targetApi="24" />
      

      在 kotlin 中:

          binding.timerExpireTime.apply {
              base = SystemClock.elapsedRealtime()
              start()
          }
      

      【讨论】:

        【解决方案8】:

        对于未来的读者,您可以使用Kotlin 中内置的timer 内联函数。

        示例:

        import kotlin.concurrent.timer
        ....
        ....
        timer(initialDelay = 1000L, period = 1000L ) {
             launch {
                executeTask()
             }
        }
        

        【讨论】:

          【解决方案9】:

          CountDownTimer 在 Kotlin 中:

          object: CountDownTimer(3000, 1000){
              override fun onTick(p0: Long) {}
              override fun onFinish() {
                  //add your code here
              }
           }.start()
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-12-25
            • 2012-09-07
            • 1970-01-01
            • 1970-01-01
            • 2011-02-06
            • 1970-01-01
            • 2022-11-10
            • 1970-01-01
            相关资源
            最近更新 更多