简体   繁体   English

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

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

There's applications I'd like to write where I'd like some things to occur on a schedule.我想编写一些应用程序,其中我希望某些事情按计划发生。

Polling a URL for updates every few minutes seems to be a fairly common use case.每隔几分钟轮询一次 URL 更新似乎是一个相当常见的用例。 In this particular case, though, I'm just trying to implement a clock.不过,在这种特殊情况下,我只是想实现一个时钟。

This works:这有效:

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

But I had to import java.util.Timer , so it's not going to be portable.但我必须导入java.util.Timer ,所以它不会是可移植的。

Jetpack Compose can do animation, so it surely has its own timer somewhere , implying there should be some portable way to do this as well, but I can't seem to figure it out. Jetpack Compose 可以做动画,所以它肯定在某处有自己的计时器,这意味着也应该有一些可移植的方法来做到这一点,但我似乎无法弄清楚。

Is there a cross-platform way to get a timer for this purpose?有没有一种跨平台的方法可以为此目的获取计时器?

In Compose you can use LaunchedEffect - it's a side effect which is run on a coroutine scope, so you can use delay inside, like this:在 Compose 中,您可以使用LaunchedEffect - 它是在协程作用域上运行的副作用,因此您可以在内部使用delay ,如下所示:

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

I just wanted to share an alternative I have experimented with in case someone else thinks of it and encounters the same issues I have.我只是想分享一个我尝试过的替代方案,以防其他人想到它并遇到我遇到的相同问题。 Here is the naive implementation:这是天真的实现:

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

Assuming you want a precission of up to a second, this snippet will cause the LaunchedEffect to update a second after remainingTime is updated, updating remainingTime in relation with the current time in milliseconds.假设您想要精确到一秒,此代码段将导致LaunchedEffect在更新remainingTime后更新一秒,更新与当前时间相关的remainingTime (以毫秒为单位)。 This basically creates a loop.这基本上创建了一个循环。 Wrapping this logic in a @Composable is good as it prevents the excessive re-composition this would cause if you embedded your LaunchedEffect in a large component tree.将此逻辑包装在@Composable中很好,因为它可以防止在大型组件树中嵌入LaunchedEffect时可能导致的过度重新组合。

This works, but there is a catch: you will eventually notice your timer is skipping seconds.这行得通,但有一个问题:您最终会注意到您的计时器正在跳秒。 This happens because there will be some extra delay between the assignment of a new value to the remainingTime variable and the re-execution of LaunchedEffect which will essentially mean there is more than a second between updates.发生这种情况是因为在将新值分配给remainingTime变量和重新执行LaunchedEffect之间会有一些额外的延迟,这实际上意味着更新之间有超过一秒的时间。

Here is an improved implementation of the above:这是上述内容的改进实现:

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

We simply subtract the time it took for LaunchedEffect to re-execute from the intended delay time.我们只需从预期的延迟时间中减去LaunchedEffect重新执行所花费的时间。 This will avoid your timer skipping seconds.这将避免您的计时器跳过秒数。

The extra delay should not be an issue for the implementation in the accepted answer.额外的延迟不应该成为已接受答案中实施的问题。 The only advantage of this approach I noticed is that the loop will stop running once we navigate away from the screen.我注意到这种方法的唯一优点是,一旦我们离开屏幕,循环就会停止运行。 In my tests, the while loop with a true condition kept running when navigating to another Activity.在我的测试中,当导航到另一个 Activity 时,具有true条件的 while 循环继续运行。

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

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