Как я могу реализовать таймер портативным способом в 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 умеет делать анимацию, поэтому у него наверняка где- то есть собственный таймер , подразумевая, что должен быть какой-то портативный способ сделать это, но я не могу понять это.
Есть ли кроссплатформенный способ получить таймер для этой цели?
3 ответа
В 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()
}
}
Предполагая, что вам нужна точность до секунды, этот фрагмент заставит обновиться через секунду после обновления, обновляя его по отношению к текущему времени в миллисекундах. Это в основном создает петлю. Оборачивая эту логику в
Это работает, но есть одна загвоздка: в конце концов вы заметите, что ваш таймер пропускает секунды. Это происходит потому, что будет некоторая дополнительная задержка между присвоением нового значения
Вот улучшенная реализация вышеизложенного:
@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()
}
}
Мы просто вычитаем время, затраченное на
Дополнительная задержка не должна быть проблемой для реализации в принятом ответе. Единственное преимущество этого подхода, которое я заметил, заключается в том, что цикл перестанет работать, как только мы уйдем от экрана. В моих тестах цикл 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
}
}