You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Jetpack Compose开发计时器应用?

Jetpack Compose 计时器实现方案

整体实现分为三层,逻辑清晰没有冗余代码:数据层用Room持久化历史计时记录,ViewModel托管计时状态和业务逻辑,Compose实现UI层渲染和交互。

1. 引入必要依赖

在module级别的build.gradle.kts中添加如下依赖:

dependencies {
    // Compose 基础依赖
    implementation(platform("androidx.compose:compose-bom:2024.03.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
    
    // Room 数据库依赖
    implementation("androidx.room:room-runtime:2.6.1")
    kapt("androidx.room:room-compiler:2.6.1")
    implementation("androidx.room:room-ktx:2.6.1")
}

2. 实现数据层(Room持久化)

2.1 定义计时记录实体类

@Entity
data class TimeRecord(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    // 本次计时总时长,单位秒
    val durationSeconds: Int,
    // 计时结束的时间戳
    val createTime: Long
)

2.2 定义Dao操作接口

@Dao
interface TimeRecordDao {
    // 插入完成的计时记录
    @Insert
    suspend fun insert(record: TimeRecord)

    // 可选:查询所有历史记录
    @Query("SELECT * FROM TimeRecord ORDER BY createTime DESC")
    suspend fun getAllRecords(): List<TimeRecord>
}

2.3 定义数据库单例类

@Database(entities = [TimeRecord::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun timeRecordDao(): TimeRecordDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "timer-db"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

3. 实现ViewModel核心逻辑

托管计时状态、计时任务、数据库写入操作,不受页面配置变更(如屏幕旋转)影响:

class TimerViewModel(application: Application) : AndroidViewModel(application) {
    // 已计时的秒数,可观测状态
    var elapsedTime by mutableIntStateOf(0)
        private set
    // 计时器运行状态,可观测状态
    var isRunning by mutableStateOf(false)
        private set
    
    private val db = AppDatabase.getInstance(application)
    private var timerJob: Job? = null

    // 启动/停止计时器切换
    fun toggleTimer() {
        if (isRunning) {
            // 停止计时任务
            timerJob?.cancel()
            // 将本次计时时长写入数据库
            viewModelScope.launch {
                db.timeRecordDao().insert(
                    TimeRecord(
                        durationSeconds = elapsedTime,
                        createTime = System.currentTimeMillis()
                    )
                )
            }
        } else {
            // 启动每秒更新的计时任务
            timerJob = viewModelScope.launch {
                while (isActive) {
                    delay(1000L)
                    elapsedTime += 1
                }
            }
        }
        isRunning = !isRunning
    }

    // 可选:重置计时的方法,按需调用
    fun resetTimer() {
        timerJob?.cancel()
        isRunning = false
        elapsedTime = 0
    }
}

4. 实现Compose UI层

代码完全贴合需求,没有多余逻辑:

@Composable
fun TimerScreen(
    viewModel: TimerViewModel = viewModel()
) {
    // 将秒数格式化为00:00的展示格式
    val timeText = remember(viewModel.elapsedTime) {
        val minutes = viewModel.elapsedTime / 60
        val seconds = viewModel.elapsedTime % 60
        String.format("%02d:%02d", minutes, seconds)
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        // 顶部计时数值展示
        Text(
            text = timeText,
            fontSize = 64.sp,
            fontWeight = FontWeight.Bold
        )
        Spacer(modifier = Modifier.height(48.dp))
        // 启动/停止按钮
        Button(
            onClick = { viewModel.toggleTimer() },
            modifier = Modifier.fillMaxWidth(),
            shape = RoundedCornerShape(8.dp),
            contentPadding = PaddingValues(vertical = 16.dp)
        ) {
            Text(
                text = if (viewModel.isRunning) "停止计时器" else "启动计时器",
                fontSize = 20.sp
            )
        }
    }
}

注意事项

  • 上述实现为前台活跃场景下的基础版本,如果需要应用退到后台后仍能精准计时,可补充前台Service逻辑避免系统杀死进程
  • 所有Room数据库操作都使用suspend方法放到协程中执行,不会阻塞主线程

内容的提问来源于stack exchange,提问作者Connor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 19:09:03