如何基于Room、LiveData与ViewModel实现RecyclerView倒计时实时更新?
实现RecyclerView倒计时实时更新方案
核心思路
不再将timeLeft(剩余时间)存储到数据库,而是存储任务的截止时间戳,在UI层实时计算剩余时间,并通过定时刷新实现倒计时效果,避免频繁操作数据库。
步骤1:修改Task实体类
替换timeLeft为targetTime(存储截止时间的毫秒级时间戳),剩余时间由UI实时计算:
@Parcelize @Entity(tableName = "task_table") data class Task( @PrimaryKey(autoGenerate = true) val id: Int, val date: String, val content: String, val targetTime: Long, // 新增:任务截止时间戳(毫秒) var isDone : Boolean ):Parcelable
步骤2:更新Dao层
调整查询排序字段为targetTime,并将返回类型改为LiveData<List<Task>>(Room返回的列表不可变,避免使用MutableList):
@Dao interface TaskDao { @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun addTask(task :Task) @Delete suspend fun deleteTask(task : Task) @Update suspend fun updateTask(task : Task) @Query("SELECT * FROM task_table order by targetTime ASC") fun readAllData(): LiveData<List<Task>> }
步骤3:同步更新Repository和ViewModel
将对应字段类型同步改为LiveData<List<Task>>:
Repository
class TaskRepository (private val taskDao : TaskDao){ val readAllData : LiveData<List<Task>> = taskDao.readAllData() suspend fun addTask(task : Task){ taskDao.addTask(task) } suspend fun deleteTask(task : Task) { taskDao.deleteTask(task) } suspend fun updateTask(task:Task) { taskDao.updateTask(task) } }
ViewModel
class TaskViewModel(application: Application) : AndroidViewModel(application) { val readAllData: LiveData<List<Task>> private val repository: TaskRepository init { val taskDao = TaskDatabase.getDatabase(application).taskDao() repository = TaskRepository(taskDao) readAllData = repository.readAllData } fun addTask(task: Task) { viewModelScope.launch(Dispatchers.IO) { repository.addTask(task) } } fun updateTask(task: Task) { viewModelScope.launch(Dispatchers.IO) { repository.updateTask(task) } } fun deleteTask(task: Task) { viewModelScope.launch(Dispatchers.IO) { repository.deleteTask(task) } } }
步骤4:修改RecyclerView Adapter
移除对timeLeft的依赖,实时计算剩余时间:
package com.example.superbtodo.fragments.list import android.annotation.SuppressLint import android.graphics.Paint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.RadioButton import android.widget.RelativeLayout import android.widget.TextView import androidx.navigation.findNavController import androidx.recyclerview.widget.RecyclerView import com.example.superbtodo.R import com.example.superbtodo.data.Task class ListAdapter() : RecyclerView.Adapter<ListAdapter.ViewHolder>() { private var tasks = emptyList<Task>() inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val contentTextView = itemView.findViewById(R.id.contentTxt) as TextView val timeTextView = itemView.findViewById(R.id.timeTxt) as TextView val timeLeftTextView = itemView.findViewById(R.id.timeLeftTxt) as TextView var isDoneCheckBox = itemView.findViewById(R.id.checkBtn) as RadioButton val taskLayout = itemView.findViewById(R.id.taskLayout) as RelativeLayout } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val itemView = LayoutInflater.from(parent.context).inflate(R.layout.task_layout, parent, false) return ViewHolder(itemView) } @SuppressLint("ResourceAsColor") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val currentItem = tasks[position] holder.contentTextView.text = currentItem.content holder.timeTextView.text = currentItem.date // 实时计算并显示剩余时间 holder.timeLeftTextView.text = calculateTimeLeft(currentItem.targetTime) holder.isDoneCheckBox.isChecked = currentItem.isDone holder.taskLayout.setOnClickListener { val action = ListFragmentDirections.actionListFragmentToUpdateTaskDialogFragment(currentItem) holder.itemView.findNavController().navigate(action) } if (holder.isDoneCheckBox.isChecked) { holder.timeLeftTextView.visibility = View.GONE holder.contentTextView.apply { paintFlags = paintFlags or Paint.STRIKE_THRU_TEXT_FLAG setTextColor(R.color.gone) } holder.timeTextView.apply { paintFlags = paintFlags or Paint.STRIKE_THRU_TEXT_FLAG setTextColor(R.color.gone) } } else { holder.timeLeftTextView.visibility = View.VISIBLE holder.contentTextView.apply { paintFlags = 0 setTextColor(R.color.black) } holder.timeTextView.apply { paintFlags = 0 setTextColor(R.color.black) } } } // 计算剩余时间的工具方法 private fun calculateTimeLeft(targetTime: Long): String { val currentTime = System.currentTimeMillis() val diff = targetTime - currentTime return if (diff <= 0) { "已截止" } else { val days = diff / (1000 * 60 * 60 * 24) val hours = (diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) val minutes = (diff % (1000 * 60 * 60)) / (1000 * 60) val seconds = (diff % (1000 * 60)) / 1000 String.format("%02d天%02d时%02d分%02d秒", days, hours, minutes, seconds) } } override fun getItemCount(): Int = tasks.size fun setData(task: List<Task>) { this.tasks = task notifyDataSetChanged() } fun getTaskAt(position: Int): Task = tasks[position] }
步骤5:在Fragment中实现定时刷新
在列表Fragment的onViewCreated方法中,启动协程每秒刷新一次列表,触发倒计时更新:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] val adapter = ListAdapter() recyclerView.adapter = adapter // 观察任务列表变化 taskViewModel.readAllData.observe(viewLifecycleOwner) { tasks -> adapter.setData(tasks) } // 每秒刷新一次倒计时 viewLifecycleOwner.lifecycleScope.launch { while (isActive) { adapter.notifyItemRangeChanged(0, adapter.itemCount) delay(1000) } } }
步骤6:数据库迁移(可选)
如果已有旧版本数据库,需要添加Room迁移规则,移除旧的timeLeft字段并新增targetTime:
// 在TaskDatabase中添加迁移规则 val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { // SQLite不支持直接删除列,需创建新表迁移数据 database.execSQL("CREATE TABLE task_table_new (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, date TEXT NOT NULL, content TEXT NOT NULL, targetTime INTEGER NOT NULL, isDone INTEGER NOT NULL)") database.execSQL("INSERT INTO task_table_new (id, date, content, targetTime, isDone) SELECT id, date, content, 0, isDone FROM task_table") database.execSQL("DROP TABLE task_table") database.execSQL("ALTER TABLE task_table_new RENAME TO task_table") } } // 创建数据库时添加迁移 fun getDatabase(context: Context): TaskDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, TaskDatabase::class.java, "task_database" ) .addMigrations(MIGRATION_1_2) .build() INSTANCE = instance instance } }
补充说明
- 添加/更新任务时,需将用户选择的截止时间转换为毫秒级时间戳(
Calendar.getInstance().timeInMillis)存入targetTime字段。 - 使用
lifecycleScope启动定时协程,会在Fragment销毁时自动取消,避免内存泄漏。
内容的提问来源于stack exchange,提问作者bovietvidai
相关产品推荐
相关产品推荐

