繁体   English   中英

如何在 Jetpack Compose 中以可移植的方式实现计时器?

[英]How can I implement a timer in a portable way in Jetpack Compose?

我想编写一些应用程序,其中我希望某些事情按计划发生。

每隔几分钟轮询一次 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"
        )
    }
}

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

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

有没有一种跨平台的方法可以为此目的获取计时器?

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

var ticks by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
    while(true) {
        delay(1.seconds)
        ticks++
    }
}

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

@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()
    }
}

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

这行得通,但有一个问题:您最终会注意到您的计时器正在跳秒。 发生这种情况是因为在将新值分配给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()
    }
}

我们只需从预期的延迟时间中减去LaunchedEffect重新执行所花费的时间。 这将避免您的计时器跳过秒数。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM