如何在Jetpack Compose中实现HH:MM:SS倒计时并在LazyColumn显示
Jetpack Compose 简易倒计时实现
核心逻辑拆分
- 状态管理:用Compose内置的状态持有类存储输入值、剩余时长、运行标记,绑定页面生命周期的协程作用域跑计时任务,避免内存泄漏。
- 输入处理:三个独立输入框分别接收小时、分钟、秒参数,做输入过滤,仅允许数字输入,同时做范围限制:小时上限23,分钟、秒上限59,拦截非法值。
- 计时实现:启动时将输入的三类时间参数统一转换为总秒数,在协程中以1秒为间隔逐次扣减剩余时长,直到剩余时长为0时自动停止计时、更新运行状态。
- 格式转换:剩余总秒数通过除法、取模运算拆分出时、分、秒三段数值,用字符串格式化补前导零,直接输出
HH:MM:SS格式的展示文本。 - 列表承载:输入模块、操作按钮、倒计时展示内容全部作为
LazyColumn的列表项插入即可,Compose状态机制会自动追踪依赖,仅更新时间文本对应的节点,不会触发整个列表重绘。
可直接运行的核心代码
@Composable fun CountDownTimerScreen() { // 状态声明 var hourInput by remember { mutableStateOf("") } var minuteInput by remember { mutableStateOf("") } var secondInput by remember { mutableStateOf("") } var remainSeconds by remember { mutableLongStateOf(0L) } var isTimerRunning by remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp, vertical = 24.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { // 时间输入区域 item { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { // 小时输入框 OutlinedTextField( value = hourInput, onValueChange = { input -> val filtered = input.filter { it.isDigit() } val numVal = filtered.toIntOrNull() ?: 0 hourInput = if (numVal > 23) "23" else filtered }, label = { Text("小时") }, modifier = Modifier.weight(1f), singleLine = true ) // 分钟输入框 OutlinedTextField( value = minuteInput, onValueChange = { input -> val filtered = input.filter { it.isDigit() } val numVal = filtered.toIntOrNull() ?: 0 minuteInput = if (numVal > 59) "59" else filtered }, label = { Text("分钟") }, modifier = Modifier.weight(1f), singleLine = true ) // 秒输入框 OutlinedTextField( value = secondInput, onValueChange = { input -> val filtered = input.filter { it.isDigit() } val numVal = filtered.toIntOrNull() ?: 0 secondInput = if (numVal > 59) "59" else filtered }, label = { Text("秒") }, modifier = Modifier.weight(1f), singleLine = true ) } } // 启动按钮 item { Button( onClick = { if (isTimerRunning) return@Button // 计算总时长 val totalSec = (hourInput.toLongOrNull() ?: 0) * 3600 + (minuteInput.toLongOrNull() ?: 0) * 60 + (secondInput.toLongOrNull() ?: 0) if (totalSec <= 0) return@Button remainSeconds = totalSec isTimerRunning = true // 启动计时协程 coroutineScope.launch { while (remainSeconds > 0 && isTimerRunning) { delay(1000) remainSeconds-- } isTimerRunning = false } }, modifier = Modifier.fillMaxWidth(), enabled = !isTimerRunning ) { Text(if (isTimerRunning) "倒计时进行中" else "启动倒计时") } } // 倒计时展示 item { val displayH = remainSeconds / 3600 val displayM = (remainSeconds % 3600) / 60 val displayS = remainSeconds % 60 val formatTime = String.format("%02d:%02d:%02d", displayH, displayM, displayS) Text( text = formatTime, style = MaterialTheme.typography.displayLarge, modifier = Modifier .fillMaxWidth() .padding(vertical = 32.dp) .wrapContentWidth(Alignment.CenterHorizontally) ) } } }
实现注意事项
- 不要自定义全局协程作用域跑计时任务,
rememberCoroutineScope()返回的作用域和当前Composable生命周期绑定,页面退出时会自动取消协程,不会产生内存泄漏。 - 如果需要扩展暂停、重置功能,只需要新增对应操作逻辑:暂停时将
isTimerRunning设为false即可中断协程循环,重置时将remainSeconds置0、清空输入值、更新运行状态即可。 - 如果需要更顺滑的毫秒级倒计时效果,可将总时长单位换为毫秒,delay时长改为100ms,格式化时取整到秒即可,不会有明显性能损耗。
内容的提问来源于stack exchange,提问作者Spiderman
相关产品推荐
相关产品推荐

