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

问题描述 投票:0回答:4

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

每隔几分钟轮询一次 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 可以做动画,所以它肯定有自己的计时器某处,这意味着也应该有一些便携式方法来做到这一点,但我似乎无法弄清楚。

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

kotlin timer android-jetpack-compose compose-desktop compose-multiplatform
4个回答
35
投票

在 Compose 中,您可以使用

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

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

4
投票

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

@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 循环继续运行。


1
投票
@Composable
fun TimerTicks(
    initTick: Long = 1_000L,
    interval: Long = 1_000L,
    content: @Composable (tickTime: Long) -> Unit
) {

    var ticks by remember(initTick) {
        mutableStateOf(initTick)
    }

    content.invoke(ticks)

    LaunchedEffect(ticks) {
        val diff = ticks + interval
        delay(interval)
        ticks = diff
    }
}

0
投票

试试这个

@Composable
fun TimerScreen() {
    val timerConverter = remember {
        mutableStateOf("")
    }
    val lastConnection = remember {
        mutableStateOf(System.currentTimeMillis())
    }
    LaunchedEffect(key1 = timerConverter.value) {
        delay(1000)
        timerConverter.value =
            converting(1000 * 60 * 60 * 12L - (System.currentTimeMillis() - lastConnection.value))
    }
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.background)
    ) {

        Text(
            modifier = Modifier
                .fillMaxWidth()
                .padding(
                    top = 15.dp,
                    bottom = 10.dp,
                ),
            text = timerConverter.value,
            style = TextStyle(
                textAlign = TextAlign.Center,
                fontWeight = FontWeight.ExtraBold,
                fontSize = 25.sp,
                color = MaterialTheme.colorScheme.onBackground
            )
        )
    }
}

fun converting(millis: Long): String =
    String.format(
        "Hours %02d : Minutes %02d : Seconds %02d",
        TimeUnit.MILLISECONDS.toHours(millis),
        TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(
            TimeUnit.MILLISECONDS.toHours(millis)
        ),
        TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(
            TimeUnit.MILLISECONDS.toMinutes(millis)
        )
    )

© www.soinside.com 2019 - 2024. All rights reserved.