Android AppCompatAutoCompleteTextView无匹配默认项及词高亮问题
AppCompatAutoCompleteTextView 联想功能优化实现
直接覆盖两个核心需求:无匹配结果兜底提示、匹配关键词加粗。
核心实现思路
原生ArrayAdapter的过滤逻辑和视图渲染不支持自定义扩展,直接继承ArrayAdapter重写过滤逻辑和item渲染逻辑即可,不需要额外引入第三方控件。
- 过滤逻辑:执行常规关键词匹配后,如果匹配列表为空,自动把预设的
Other兜底项加入返回结果 - 视图渲染:渲染每个下拉项时,提取当前输入的关键词,把文本中对应位置的片段设置为粗体样式
具体代码实现
1. 自定义联想适配器
import android.content.Context import android.graphics.Typeface import android.text.Spannable import android.text.SpannableString import android.text.style.StyleSpan import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.Filter import android.widget.TextView class TopicSuggestAdapter( context: Context, itemLayoutRes: Int, private val allSuggestItems: Array<String> ) : ArrayAdapter<String>(context, itemLayoutRes, allSuggestItems.toMutableList()) { private val currentDisplayList = mutableListOf<String>() private var currentInput = "" override fun getCount(): Int = currentDisplayList.size override fun getItem(position: Int): String = currentDisplayList[position] override fun getFilter(): Filter = object : Filter() { override fun performFiltering(input: CharSequence?): FilterResults { currentInput = input?.toString()?.trim() ?: "" val result = FilterResults() val matchItems = mutableListOf<String>() when { currentInput.isEmpty() -> { // 输入为空时展示全部联想项 matchItems.addAll(allSuggestItems) } else -> { // 常规匹配:遍历词库匹配包含输入关键词的项,忽略大小写 allSuggestItems.forEach { item -> if (item != "Other" && item.contains(currentInput, true)) { matchItems.add(item) } } // 无匹配结果时加入兜底项Other if (matchItems.isEmpty()) { matchItems.add("Other") } } } result.values = matchItems result.count = matchItems.size return result } override fun publishResults(constraint: CharSequence?, results: FilterResults?) { currentDisplayList.clear() results?.values?.let { currentDisplayList.addAll(it as List<String>) } if (results?.count ?: 0 > 0) notifyDataSetChanged() else notifyDataSetInvalidated() } } override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val itemView = super.getView(position, convertView, parent) val tvContent = itemView.findViewById<TextView>(android.R.id.text1) val itemText = currentDisplayList[position] // 非兜底项才做关键词加粗处理 if (currentInput.isNotEmpty() && itemText != "Other") { val keywordStart = itemText.indexOf(currentInput, ignoreCase = true) if (keywordStart >= 0) { val spannable = SpannableString(itemText) spannable.setSpan( StyleSpan(Typeface.BOLD), keywordStart, keywordStart + currentInput.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) tvContent.text = spannable return itemView } } tvContent.text = itemText return itemView } }
2. 替换原有原生适配器
把原来初始化ArrayAdapter的逻辑替换为自定义适配器即可:
binding.topicEt.apply { threshold = 1 // 输入1个字符即触发联想,可按需调整为0或其他值 val topicArray = resources.getStringArray(R.array.feedback_topic_array) adapter = TopicSuggestAdapter( context, android.R.layout.simple_dropdown_item_1line, topicArray ) // 需要页面加载后默认展开下拉则保留,否则删除 post { showDropDown() } }
可选调整点
- 匹配规则修改:如果需要前缀匹配而不是全局包含匹配,把过滤逻辑里的
item.contains(currentInput, true)替换为item.startsWith(currentInput, true)即可 - 样式扩展:如果需要给匹配关键词加颜色、字号调整,把
StyleSpan(Typeface.BOLD)替换为ForegroundColorSpan、AbsoluteSizeSpan等对应Span即可 - 避免硬编码:可以把兜底项
Other提取为字符串资源,替换Adapter里的硬编码值,方便后续多语言适配
内容的提问来源于stack exchange,提问作者Seb
相关产品推荐
相关产品推荐

