如何在 Jetpack Compose 中以便携式方式实现计时器?

Tre*_*kaz 18 timer kotlin android-jetpack-compose compose-desktop compose-multiplatform

我想编写一些应用程序,让一些事情按计划进行。

每隔几分钟轮询一次 URL 进行更新似乎是一个相当常见的用例。不过,在这种特殊情况下,我只是想实现一个时钟。

这有效:

@Composable
fun App() {
    var ticks by remember { mutableStateOf(0) }

    // Not 100% happy about this unused variable either
    val timer = remember {
        Timer().apply {
            val task = object : TimerTask() {
                override fun run() {
                    ticks++
                }
            }
            scheduleAtFixedRate(task, 1000L, 1000L)
        }
    }

    MaterialTheme {
        Text(
            // A real application would format this number properly,
            // but that's a different question
            text = "$ticks"
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

但我必须导入java.util.Timer,所以它不会是可移植的。

Jetpack Compose 可以做动画,所以它肯定在某个地方有自己的计时器,这意味着也应该有一些便携的方法来做到这一点,但我似乎无法弄清楚。

有没有跨平台的方法来为此目的获取计时器?

Phi*_*hov 43

在 Compose 中,您可以使用LaunchedEffect- 这是在协程作用域上运行的副作用,因此您可以delay在内部使用,如下所示:

var ticks by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
    while(true) {
        delay(1.seconds)
        ticks++
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @SardorbekMuhammadjonov,您可以使用“key”参数重置启动效果,例如[this](https://gist.github.com/PhilipDukhov/685d121ba9be64827465e50a720b61b7) (4认同)

arg*_*iwi 6

我只是想分享一个我尝试过的替代方案,以防其他人想到它并遇到与我相同的问题。这是简单的实现:

@Composable
fun Countdown(targetTime: Long, content: @Composable (remainingTime: Long) -> Unit) {
    var remainingTime by remember(targetTime) {
        mutableStateOf(targetTime - System.currentTimeMillis())
    }

    content.invoke(remainingTime)

    LaunchedEffect(remainingTime) {
        delay(1_000L)
        remainingTime = targetTime - System.currentTimeMillis()
    }
}

Run Code Online (Sandbox Code Playgroud)

假设您想要高达一秒的精度,此代码片段将导致在更新LaunchedEffect后更新一秒,更新与当前时间(以毫秒为单位)相关。这基本上创建了一个循环。将此逻辑包装在 a 中是很好的,因为它可以防止在将您嵌入到大型组件树中时导致的过度重新组合。remainingTimeremainingTime@ComposableLaunchedEffect

这可行,但有一个问题:您最终会注意到计时器正在跳过几秒。发生这种情况是因为向变量分配新值remainingTime和重新执行之间会存在一些额外的延迟LaunchedEffect,这本质上意味着更新之间的时间间隔超过一秒。

这是上述内容的改进实现:

@Composable
fun Countdown(targetTime: Long, content: @Composable (remainingTime: Long) -> Unit) {
    var remainingTime by remember(targetTime) {
        mutableStateOf(targetTime - System.currentTimeMillis())
    }

    content.invoke(remainingTime)

    LaunchedEffect(remainingTime) {
        val diff = remainingTime - (targetTime - System.currentTimeMillis())
        delay(1_000L - diff)
        remainingTime = targetTime - System.currentTimeMillis()
    }
}
Run Code Online (Sandbox Code Playgroud)

LaunchedEffect我们只需从预期延迟时间中减去重新执行所需的时间即可。这将避免你的计时器跳秒。

额外的延迟不应成为已接受答案中实施的问题。我注意到这种方法的唯一优点是,一旦我们离开屏幕,循环就会停止运行。在我的测试中,当导航到另一个活动时,带有条件的 while 循环true继续运行。