如何修复动态更新时RecycleView输出错误

问题描述 投票:0回答:1

我正在学习android开发,我编写了一个任务调度程序应用程序。我有一个侧边栏,其中的日期至少有 1 个任务。主要活动有一个回收视图,当单击侧边栏中的日期和启动应用程序并选择当前日期时,该视图都会显示任务。如果我在打开文件时更改任务,那么一切都会正常。但是,如果我更改日期,那么在更新时,RecycleView 开始滞后,给出今天的日期,然后给出我在面板中选择的日期的任务。我使用 LiveData 通过 Room 处理数据。如何修复滞后,以便在切换 RecycleView 面板中的日期时正确显示。 主要活动:

class MainActivity : AppCompatActivity() {
    private lateinit var drawerLayout: DrawerLayout
    private var bottomSheetNewTask: NewTaskFragment? = null
    private lateinit var recyclerView: RecyclerView
    private lateinit var taskAdapter: TaskAdapter
    private lateinit var taskViewModel: TaskViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setAppTheme()

        enableEdgeToEdge()
        setContentView(R.layout.activity_main)

        setupWindowInsets()

        setupViewModel()
        setupRecyclerView()
        setupObservers()
        setupNavigationDrawer()
        setupAddTaskButton()
    }

    private fun setAppTheme() {
        val sharedPreferences = getSharedPreferences("app_preferences", MODE_PRIVATE)
        val isDarkMode = sharedPreferences.getBoolean("dark_mode", false)
        AppCompatDelegate.setDefaultNightMode(if (isDarkMode) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
    }

    private fun setupWindowInsets() {
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }

    private fun setupViewModel() {
        val repository = TaskRepository(TaskDatabase.getDatabase(this).taskDao())
        val viewModelFactory = TaskViewModelFactory(repository)
        taskViewModel = ViewModelProvider(this, viewModelFactory)[TaskViewModel::class.java]
    }

    private fun setupRecyclerView() {
        recyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        taskAdapter = TaskAdapter(emptyList(), taskViewModel) { task -> openTaskDetails(task) }
        recyclerView.adapter = taskAdapter
    }

    private fun setupObservers() {
        val today = System.currentTimeMillis()
        observeTasks(today)
        taskViewModel.getAllTaskDates().observe(this) { dates -> updateSidePanel(dates) }
    }

    private fun setupNavigationDrawer() {
        drawerLayout = findViewById(R.id.drawer_layout)
        val navigationView: NavigationView = findViewById(R.id.navigation_view)

        navigationView.setNavigationItemSelectedListener { menuItem ->
            when (menuItem.itemId) {
                R.id.nav_stat -> startActivity(Intent(this, StatisticsActivity::class.java))
                R.id.nav_sett -> startActivity(Intent(this, SettingsActivity::class.java))
            }
            drawerLayout.closeDrawers()
            true
        }

        findViewById<ImageButton>(R.id.menuBtn).setOnClickListener {
            if (!drawerLayout.isDrawerOpen(navigationView)) {
                drawerLayout.openDrawer(navigationView)
            } else {
                drawerLayout.closeDrawer(navigationView)
            }
        }
    }

    private fun setupAddTaskButton() {
        findViewById<ImageButton>(R.id.addTask).setOnClickListener {
            if (bottomSheetNewTask == null) {
                bottomSheetNewTask = NewTaskFragment()
            }
            bottomSheetNewTask?.show(supportFragmentManager, bottomSheetNewTask?.tag)
        }
    }

    override fun onBackPressed() {
        if (bottomSheetNewTask?.isVisible == true) {
            bottomSheetNewTask?.dismiss()
        } else {
            super.onBackPressed()
        }
    }

    private fun openTaskDetails(task: Task) {
        val bottomSheet = TaskFragment.newInstance(task)
        bottomSheet.show(supportFragmentManager, "TaskDetails")
    }

    private fun updateSidePanel(dates: List<String>) {
        val navigationView = findViewById<NavigationView>(R.id.navigation_view)
        val menu = navigationView.menu
        val dynamicGroupId = 1

        menu.removeGroup(dynamicGroupId)
        dates.forEach { date ->
            menu.add(dynamicGroupId, Menu.NONE, Menu.NONE, date).setOnMenuItemClickListener {
                onDateSelected(date)
                true
            }
        }
    }

    private fun onDateSelected(date: String) {
        val dateInMillis = convertDateToMillis(date)
        observeTasks(dateInMillis)
    }

    private fun convertDateToMillis(dateString: String): Long {
        val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
        return dateFormat.parse(dateString)?.time ?: throw IllegalArgumentException("Invalid date format")
    }

    private fun observeTasks(dateInMillis: Long) {
        taskViewModel.getTasksByDate(dateInMillis).observe(this) { tasks ->
            taskAdapter.updateTasks(tasks)
            findViewById<LinearLayout>(R.id.start_text).isVisible = tasks.isEmpty()
        }
    }
}

我的TaskDao:

@Dao
interface TaskDao {
    @Query("SELECT * FROM tasks WHERE createdAt BETWEEN :startOfDay AND :endOfDay ORDER BY isCompleted ASC, priority DESC")
    fun getTasksByDate(startOfDay: Long, endOfDay: Long): LiveData<List<Task>>

    @Query("SELECT DISTINCT STRFTIME('%Y-%m-%d', createdAt / 1000, 'unixepoch') FROM tasks ORDER BY createdAt DESC")
    fun getAllTaskDates(): LiveData<List<String>>

    @Query("SELECT createdAt FROM tasks GROUP BY createdAt ORDER BY COUNT(*) DESC LIMIT 1")
    fun getBusiestDay() : LiveData<Long>

    @Query("SELECT color, COUNT(*) AS count FROM tasks WHERE color IN ('blue', 'green', 'yellow', 'red') GROUP BY color")
    fun getTaskCountByColor(): LiveData<List<ColorCount>>

    @Query("SELECT COUNT(*) FROM tasks WHERE isCompleted = 1")
    fun getCompletedTaskCount(): LiveData<Int>

    @Query("SELECT COUNT(*) FROM tasks WHERE isCompleted = 0")
    fun getIncompleteTaskCount(): LiveData<Int>

    @Query("SELECT COUNT(*) FROM tasks WHERE priority = 1")
    fun getFirstPriorCount(): LiveData<Int>

    @Query("SELECT COUNT(*) FROM tasks WHERE priority = 2")
    fun getSecondPriorCount(): LiveData<Int>

    @Query("SELECT COUNT(*) FROM tasks WHERE priority = 3")
    fun getThirdPriorCount(): LiveData<Int>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertTask(task: Task)

    @Update
    suspend fun updateTask(task: Task)
}

data class ColorCount(
    val color: String,
    val count: Int
)

我的任务适配器:

class TaskAdapter(
    private var tasks: List<Task>,
    private val taskViewModel: TaskViewModel,
    private val onTaskClick: (Task) -> Unit
) : RecyclerView.Adapter<TaskAdapter.TaskViewHolder>() {

    fun updateTasks(newTasks: List<Task>) {
        val diffCallback = TaskDiffCallback(tasks, newTasks)
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        tasks = newTasks
        diffResult.dispatchUpdatesTo(this)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(R.layout.task_card, parent, false)
        return TaskViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
        holder.bind(tasks[position])
    }

    override fun getItemCount() = tasks.size

    inner class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val checkBox: CheckBox = itemView.findViewById(R.id.isCompl)
        private val cardView: CardView = itemView.findViewById(R.id.card)
        private val titleView: TextView = itemView.findViewById(R.id.title)
        private val descView: TextView = itemView.findViewById(R.id.desc)
        private val firstPrior: ImageView = itemView.findViewById(R.id.first_prior)
        private val secondPrior: ImageView = itemView.findViewById(R.id.second_prior)
        private val thirdPrior: ImageView = itemView.findViewById(R.id.third_prior)

        fun bind(task: Task) {
            checkBox.setOnCheckedChangeListener(null) // Удаляем слушатель
            checkBox.isChecked = task.isCompleted

            checkBox.setOnCheckedChangeListener { _, isChecked ->
                task.isCompleted = isChecked
                taskViewModel.updateTask(task)
                // Обновляем только текущий элемент
                notifyItemChanged(adapterPosition)
            }

            titleView.text = task.title
            descView.text = task.description
            setPrior(task.priority)

            cardView.setCardBackgroundColor(if (task.isCompleted) Color.GRAY else getTaskColor(task.color))

            // Обрабатываем нажатие на весь элемент
            itemView.setOnClickListener { onTaskClick(task) }
        }

        private fun getTaskColor(color: String): Int {
            return when (color) {
                "blue" -> Color.rgb(24, 136, 255)
                "green" -> Color.rgb(34, 133, 7)
                "yellow" -> Color.rgb(237, 178, 0)
                "red" -> Color.rgb(225, 7, 7)
                else -> Color.rgb(24, 136, 255)
            }
        }

        private fun setPrior(prior: Int) {
            firstPrior.setImageResource(if (prior >= 1) R.drawable.star else R.drawable.star_outline)
            secondPrior.setImageResource(if (prior >= 2) R.drawable.star else R.drawable.star_outline)
            thirdPrior.setImageResource(if (prior >= 3) R.drawable.star else R.drawable.star_outline)
        }
    }
}

我尝试更改 TaskViewHolder 中的绑定并使用 TaskDiffCallback 更新 TaskAdapter 中的任务。我认为问题是我同时显示当前日期和侧边栏的日期,但我不知道如何解决它。

android-recyclerview android-room android-livedata
1个回答
0
投票

在我的TaskRepository中使用distinctUntilChanged()解决了问题

fun getTasksByDate(startOfDay: Long, endOfDay: Long): LiveData<List<Task>> {
    return taskDao.getTasksByDate(startOfDay, endOfDay).distinctUntilChanged()
}
© www.soinside.com 2019 - 2024. All rights reserved.