如何用Kotlin扩展函数实现通用过滤适配器?安卓新手求分步指导
Kotlin 通用过滤适配器开发分步指导(含扩展函数优化实现)
核心设计思路
通用过滤适配器的目标是把所有RecyclerView列表都需要重复写的“数据维护、过滤逻辑、UI刷新”能力抽离出来,避免每做一个带搜索功能的列表就重写一遍Filterable接口、重复维护两套数据集。结合Kotlin的扩展函数特性,可以进一步把样板代码压缩到最少,新手不用写复杂的类继承就能快速实现带过滤能力的列表。
分步实现流程
第一步:搭建通用适配器的基础抽象类
先把所有适配器共有的能力封装成泛型抽象类,固定好数据维护的逻辑:
- 内部自动维护两份数据集:全量原始数据集、过滤后展示数据集,避免业务代码重复处理数据缓存
- 把item布局ID、View绑定逻辑做成可配置的参数,不需要子类重复实现ViewHolder创建、布局加载的通用流程
abstract class BaseFilterableAdapter<T>( private val layoutResId: Int, initialList: List<T> = emptyList() ) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable { // 原始全量数据:不会被过滤逻辑修改 private val originalList = initialList.toMutableList() // 过滤后展示的数据集 private val filteredList = initialList.toMutableList() // 留给业务侧实现的View绑定逻辑 abstract fun bindItem(holder: RecyclerView.ViewHolder, item: T, position: Int) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val itemView = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false) return object : RecyclerView.ViewHolder(itemView) {} } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val currentItem = filteredList[position] bindItem(holder, currentItem, position) } override fun getItemCount(): Int = filteredList.size }
第二步:实现可配置的通用过滤逻辑
不要把过滤规则硬编码在适配器内部,把匹配逻辑暴露成可传入的Lambda表达式,让适配器可以适配任意类型的数据过滤需求:
- 搜索框为空时自动恢复展示全量原始数据
- 过滤规则由业务侧自定义,比如匹配用户名、匹配订单号、匹配商品标签都可以,不用修改适配器源码
// 在BaseFilterableAdapter类内部补充以下逻辑 // 可自定义的匹配规则:入参为当前item、用户输入的查询文本,返回是否符合展示条件 var matchRule: ((item: T, query: String) -> Boolean) = { _, _ -> true } // 通用过滤器实现,所有子类直接复用,不用重写 private val commonFilter = object : Filter() { override fun performFiltering(constraint: CharSequence?): FilterResults { val filterResults = FilterResults() val queryStr = constraint?.toString()?.trim().orEmpty() val matchResult = if (queryStr.isEmpty()) { originalList } else { originalList.filter { matchRule(it, queryStr) } } filterResults.values = matchResult filterResults.count = matchResult.size return filterResults } override fun publishResults(constraint: CharSequence?, results: FilterResults?) { // 拿到过滤结果直接更新展示列表并刷新UI,业务侧无感知 val newList = results?.values as? List<T> ?: emptyList() filteredList.clear() filteredList.addAll(newList) notifyDataSetChanged() } } override fun getFilter(): Filter = commonFilter
注意:绝对不要在过滤流程中修改
originalList的内容,不然用户清空搜索框时就无法恢复完整的原始数据列表,这是新手写过滤适配器最容易踩的崩溃坑。
第三步:用Kotlin扩展函数减少样板代码
这部分是实现通用适配器的核心优化,通过给RecyclerView和SearchView写扩展函数,把“初始化适配器、设置布局管理器、绑定搜索监听”这些重复流程全部封装,业务侧不需要为每个列表场景单独写一个Adapter子类:
/** * RecyclerView一键绑定通用过滤适配器的扩展函数 */ fun <T> RecyclerView.bindFilterableList( layoutResId: Int, data: List<T>, matchRule: (item: T, query: String) -> Boolean, onBind: (holder: RecyclerView.ViewHolder, item: T, position: Int) -> Unit ) { // 自动补全新手容易漏写的布局管理器,默认用线性布局 if (this.layoutManager == null) { this.layoutManager = LinearLayoutManager(this.context) } // 直接实例化通用适配器,不需要创建子类 val adapter = object : BaseFilterableAdapter<T>(layoutResId, data) { override fun bindItem(holder: RecyclerView.ViewHolder, item: T, position: Int) { onBind(holder, item, position) } }.apply { this.matchRule = matchRule } this.adapter = adapter } /** * SearchView一键绑定过滤能力的扩展函数,不需要手动写TextWatcher监听 */ fun SearchView.bindToFilterAdapter(adapter: BaseFilterableAdapter<*>) { this.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { adapter.filter.filter(query) return true } override fun onQueryTextChange(newText: String?): Boolean { adapter.filter.filter(newText) return true } }) }
第四步:业务侧实际调用示例
以实现“支持按用户名搜索的用户列表”为例,在Activity/Fragment中只需要几行代码就能完成全部功能:
// 1. 实例化布局控件 val userRv: RecyclerView = findViewById(R.id.rv_user) val searchView: SearchView = findViewById(R.id.sv_search) // 2. 定义数据实体类 data class User(val name: String, val phone: String) // 3. 准备全量原始数据 val userList = listOf( User("张三", "13800138000"), User("李四", "13900139000"), User("张小明", "13700137000") ) // 4. 一行代码绑定带过滤能力的列表 userRv.bindFilterableList( layoutResId = R.layout.item_user, data = userList, // 自定义过滤规则:匹配包含搜索文本的用户名 matchRule = { user, query -> user.name.contains(query, ignoreCase = true) }, onBind = { holder, user, _ -> // 绑定item内的View holder.itemView.tv_name.text = user.name holder.itemView.tv_phone.text = user.phone } ) // 5. 绑定搜索框的过滤能力,输入时自动实时刷新列表 searchView.bindToFilterAdapter(userRv.adapter as BaseFilterableAdapter<User>)
新手常见注意事项
- 当列表数据量超过1000条时,可以在
performFiltering逻辑里做分页过滤,这个方法本身运行在工作线程,不需要额外切换线程,不会卡UI - 如果需要动态切换过滤规则(比如一会按名字搜、一会按手机号搜),直接修改adapter的
matchRule变量即可,不需要重新初始化整个适配器 - 扩展函数里拿到的context都是RecyclerView/SearchView绑定的本地Context,不会造成全局Context内存泄漏的问题
- 如果需要实现“空搜索结果展示”的能力,可以在通用适配器里加一个回调:当
filteredList.size == 0时回调业务侧展示空布局,不用每个列表自己判断数据长度
内容的提问来源于stack exchange,提问作者Samson Joe
相关产品推荐
相关产品推荐

