如何为AutoCompleteTextView的自定义ArrayAdapter实现自定义过滤器并解决UnsupportedOperationException崩溃问题
解决自定义ArrayAdapter实现Filterable时的崩溃问题
嘿,我帮你分析下这个崩溃的原因,再给你两种靠谱的修复方案:
问题根源
从堆栈跟踪能明显看到,崩溃是在调用clear()方法时触发的java.lang.UnsupportedOperationException——这是因为你传给ArrayAdapter的fornitori是不可变List(比如用listOf()创建的),而ArrayAdapter默认会直接使用传入的集合来维护内部数据。当你在publishResults里调用clear()或者add()时,本质是在尝试修改这个不可变集合,自然就抛出不支持修改的异常了。
修复方案
方案一:让ArrayAdapter维护可变数据集合
在Adapter构造时,把传入的不可变List转成可变的ArrayList,这样ArrayAdapter内部的clear()和add()操作就不会报错了。同时要维护一份原始数据的备份,避免过滤时修改原始数据:
class FornitoriAdapter(context: Context, resource: Int, fornitori: List<Fornitori>) : ArrayAdapter<Fornitori>(context, resource, ArrayList(fornitori)), Filterable { // 备份原始数据,用于过滤时遍历 private val originalFornitori = ArrayList(fornitori) override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val row: View val holder: ViewHolder if (convertView == null) { val inflater = LayoutInflater.from(context) row = inflater.inflate(R.layout.list_item, parent, false) holder = ViewHolder(row) row.tag = holder } else { row = convertView holder = row.tag as ViewHolder } val fornitore: Fornitori = getItem(position) // 改用ArrayAdapter的getItem获取当前数据 holder.descrText.text = fornitore.desc holder.codeText.text = fornitore.cod return row } private class ViewHolder constructor(row: View) { val descrText: TextView = row.findViewById(R.id.description) val codeText: TextView = row.findViewById(R.id.code) } override fun getFilter(): Filter { return customFilter } private val customFilter = object : Filter() { override fun convertResultToString(resultValue: Any?): CharSequence { return (resultValue as Fornitori).desc } override fun performFiltering(constraint: CharSequence?): FilterResults { val filteredList = mutableListOf<Fornitori>() val filterPattern = constraint?.toString()?.lowercase(Locale.ROOT) ?: "" if (constraint.isNullOrEmpty()) { filteredList.addAll(originalFornitori) } else { for (item in originalFornitori) { if (item.cod.equals(filterPattern, ignoreCase = true) || item.desc.lowercase(Locale.ROOT).startsWith(filterPattern) ) { filteredList.add(item) } } } val results = FilterResults() results.values = filteredList results.count = filteredList.size // 必须设置count,否则Filter无法正确判断结果数量 return results } override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) { clear() filterResults?.values?.let { addAll(it as Collection<Fornitori>) // 用addAll批量添加,比循环add更高效 } if (filterResults?.count ?: 0 > 0) { notifyDataSetChanged() } else { notifyDataSetInvalidated() } } } }
方案二:自己维护过滤后的数据集(更灵活)
这种方式完全绕过ArrayAdapter的默认数据管理,自己维护原始数据和过滤结果两个列表,所有UI相关方法都基于过滤后的列表实现,彻底避免修改不可变集合的问题:
class FornitoriAdapter(context: Context, resource: Int, private val originalFornitori: List<Fornitori>) : ArrayAdapter<Fornitori>(context, resource), Filterable { // 自己维护过滤后的结果列表 private var filteredFornitori: List<Fornitori> = originalFornitori override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val row: View val holder: ViewHolder if (convertView == null) { val inflater = LayoutInflater.from(context) row = inflater.inflate(R.layout.list_item, parent, false) holder = ViewHolder(row) row.tag = holder } else { row = convertView holder = row.tag as ViewHolder } val fornitore: Fornitori = filteredFornitori[position] holder.descrText.text = fornitore.desc holder.codeText.text = fornitore.cod return row } override fun getItem(position: Int): Fornitori { return filteredFornitori[position] } override fun getCount(): Int { return filteredFornitori.size } private class ViewHolder constructor(row: View) { val descrText: TextView = row.findViewById(R.id.description) val codeText: TextView = row.findViewById(R.id.code) } override fun getFilter(): Filter { return customFilter } private val customFilter = object : Filter() { override fun convertResultToString(resultValue: Any?): CharSequence { return (resultValue as Fornitori).desc } override fun performFiltering(constraint: CharSequence?): FilterResults { val filteredList = mutableListOf<Fornitori>() val filterPattern = constraint?.toString()?.lowercase(Locale.ROOT) ?: "" if (constraint.isNullOrEmpty()) { filteredList.addAll(originalFornitori) } else { for (item in originalFornitori) { if (item.cod.equals(filterPattern, ignoreCase = true) || item.desc.lowercase(Locale.ROOT).startsWith(filterPattern) ) { filteredList.add(item) } } } val results = FilterResults() results.values = filteredList results.count = filteredList.size return results } override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) { // 更新过滤后的列表 filteredFornitori = filterResults?.values as? List<Fornitori> ?: emptyList() if (filterResults?.count ?: 0 > 0) { notifyDataSetChanged() } else { notifyDataSetInvalidated() } } } }
额外注意点
- 不管用哪种方案,
performFiltering里一定要设置results.count,否则Filter无法正确判断结果数量,可能导致UI不更新 - 方案二更适合复杂的过滤场景,因为完全掌控数据的流转,不会和ArrayAdapter的内部逻辑冲突
- 你在Fragment里用
listOf()创建测试数据,确实会生成不可变List,这也是触发问题的直接原因,但最好还是在Adapter层面处理数据可变性,这样不管外部传入什么类型的List都能兼容
内容的提问来源于stack exchange,提问作者NiceToMytyuk
相关产品推荐
相关产品推荐

