如何使用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
相关产品推荐
相关产品推荐

