我想编写一些应用程序,让一些事情按计划进行。
每隔几分钟轮询一次 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 循环继续运行。
@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
}
}
试试这个
@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)
)
)