Kotlin自定义类无法继承ListAdapter问题求助(附代码)
修复TodoAdapter无法继承ListAdapter的问题
我在Kotlin开发Android待办App入门时,遇到TodoAdapter无法继承ListAdapter的问题,添加基于txt的本地持久化功能前一切正常,以下是相关代码及修复方案:
原代码展示
TodoAdapter类
class TodoAdapter ( private val todos: MutableList<Todo> ) : ListAdapter<Todo,TodoAdapter.TodoViewHolder>() { class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder { return TodoViewHolder( LayoutInflater.from(parent.context).inflate( R.layout.item_todo, parent, false ) ) } fun addTodo(todo: Todo) { todos.add(todo) notifyItemInserted(todos.size - 1) } fun deleteDoneTodos() { todos.removeAll { todo - todo.isChecked } notifyDataSetChanged() } private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked: Boolean) { if (isChecked) { tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG } else{ tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv() } } override fun onBindViewHolder(holder: TodoViewHolder, position: Int) { val curTodo = todos[position] holder.itemView.apply { tvTodoTitle.text = curTodo.title //Hier stimmt etwas nicht: tvTodoTitle Import fehlt??? cbDone.isChecked = curTodo.isChecked toggleStrikeThrough(tvTodoTitle, curTodo.isChecked) cbDone.setOnCheckedChangeListener{ _, isChecked - toggleStrikeThrough(tvTodoTitle, isChecked) curTodo.isChecked = !curTodo.isChecked } } } override fun getItemCount(): Int { return todos.size } }
Todo数据类
data class Todo( val title: String, var isChecked: Boolean = false )
MainActivity持久化相关代码
private fun setupInternalStorageRecyclerView() = binding.rvTodoItems.apply { adapter = todoAdapter layoutManager = rvTodoItems.layoutManager } private fun loadTodoItemsFromInternalStorageIntoRecyclerView() { lifecycleScope.launch { val todoItems = loadTodoItemsFromInternalStorage() todoAdapter.submitList(todoItems) } } private suspend fun loadTodoItemsFromInternalStorage(): List<Todo> { return withContext(Dispatchers.IO) { val todoItemList: MutableList<Todo> = mutableListOf<Todo>() val files = filesDir.listFiles() files?.filter { it.canRead() && it.isFile && it.name.endsWith(".txt") }?.map { val lines = it.bufferedReader().readLines() for (i in lines.indices step 2) { todoItemList.add(Todo(lines[i], lines[i+1].toBoolean())) } todoItemList } ?: mutableListOf<Todo>() } as MutableList<Todo> } private fun saveTodoItemsToInternalStorage(filename: String, todoItems: List<Todo>): Boolean { return try{ openFileOutput("$filename.txt", MODE_PRIVATE).use { stream - File(filename).printWriter().use { out - for (item in todoItems) { out.println(item.title) out.println(item.isChecked) } } } true } catch(e: IOException) { e.printStackTrace() false } }
问题原因及修复方案
核心问题点
- ListAdapter强制要求实现DiffUtil.ItemCallback:ListAdapter依赖DiffUtil做高效列表更新,必须传入DiffCallback实现类,原代码未满足该要求
- 重复维护列表数据:同时持有自定义
todos列表和ListAdapter内置列表,导致数据不一致 - Lambda语法错误:多处箭头
->被误写为-,引发编译失败 - 布局控件未正确引用:
tvTodoTitle需确保布局文件存在对应ID,并正确导入
修复后的代码
修复后的TodoAdapter
class TodoAdapter : ListAdapter<Todo, TodoAdapter.TodoViewHolder>(TodoDiffCallback()) { class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) // 实现DiffUtil.ItemCallback,ListAdapter的强制要求 class TodoDiffCallback : DiffUtil.ItemCallback<Todo>() { override fun areItemsTheSame(oldItem: Todo, newItem: Todo): Boolean { // 建议后续给Todo添加唯一id字段,此处临时用标题+状态组合判断 return oldItem.title == newItem.title && oldItem.isChecked == newItem.isChecked } override fun areContentsTheSame(oldItem: Todo, newItem: Todo): Boolean { return oldItem == newItem // Data Class自动实现equals,对比所有字段 } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder { return TodoViewHolder( LayoutInflater.from(parent.context).inflate( R.layout.item_todo, parent, false ) ) } fun addTodo(todo: Todo) { val updatedList = currentList.toMutableList().apply { add(todo) } submitList(updatedList) } fun deleteDoneTodos() { val filteredList = currentList.filterNot { it.isChecked } submitList(filteredList) } private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked: Boolean) { tvTodoTitle.paintFlags = if (isChecked) { tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG } else { tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv() } } override fun onBindViewHolder(holder: TodoViewHolder, position: Int) { val curTodo = currentList[position] holder.itemView.apply { tvTodoTitle.text = curTodo.title cbDone.isChecked = curTodo.isChecked toggleStrikeThrough(tvTodoTitle, curTodo.isChecked) cbDone.setOnCheckedChangeListener { _, isChecked -> // 用copy创建新对象,符合ListAdapter不可变数据更新原则 val updatedTodo = curTodo.copy(isChecked = isChecked) val updatedList = currentList.toMutableList().apply { this[position] = updatedTodo } submitList(updatedList) toggleStrikeThrough(tvTodoTitle, isChecked) } } } // ListAdapter已实现getItemCount,无需重写 }
修复后的MainActivity持久化代码
1. 初始化Adapter
private val todoAdapter = TodoAdapter() // 不再传入列表参数
2. 修复保存方法
private fun saveTodoItemsToInternalStorage(filename: String, todoItems: List<Todo>): Boolean { return try { openFileOutput("$filename.txt", MODE_PRIVATE).bufferedWriter().use { out -> todoItems.forEach { out.write("${it.title}\n") out.write("${it.isChecked}\n") } } true } catch (e: IOException) { e.printStackTrace() false } }
3. 修复加载方法
private suspend fun loadTodoItemsFromInternalStorage(): List<Todo> { return withContext(Dispatchers.IO) { val todoItemList = mutableListOf<Todo>() filesDir.listFiles()?.filter { it.canRead() && it.isFile && it.name.endsWith(".txt") } ?.firstOrNull()?.let { file -> val lines = file.bufferedReader().readLines() for (i in lines.indices step 2) { if (i + 1 < lines.size) { // 防止数组越界 todoItemList.add(Todo(lines[i], lines[i+1].toBoolean())) } } } todoItemList } }
内容的提问来源于stack exchange,提问作者Bennet Müller
相关产品推荐
相关产品推荐

