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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.27 14:15:11