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

如何为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 10:22:32