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

如何基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 18:24:41