You need to enable JavaScript to run this app. Kotlin中ListAdapter如何结合SearchView通过Filterable实现过滤?
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中ListAdapter如何结合SearchView通过Filterable实现过滤?

核心问题根因

你遇到的两个开发异常,本质是对ListAdapter运行机制和Filter跨线程执行逻辑的认知偏差导致的:

  • publishResults方法拿到空值的原因:直接依赖currentList作为过滤数据源是不可靠的。currentList是ListAdapter对外暴露的只读属性,如果你没有在数据提交阶段对全量数据做单独的持久化缓存,当子线程执行performFiltering读取该属性时,很容易因为内存可见性、列表未完成初始化拿到空值。
  • 过滤后界面无响应的原因有两个:一是过滤后的列表如果和原列表是同一个内存引用,ListAdapter内置的DiffUtil会直接判定列表无变化,跳过刷新流程;二是和LiveData的绑定逻辑存在冲突——LiveData每次回调都会触发submitList(全量数据),直接覆盖你之前生成的过滤结果,让列表始终展示全量内容。
可直接复用的Kotlin实现方案

整套实现完全基于ListAdapter和Filterable的原生能力,不需要依赖第三方库:

  1. Adapter内部独立维护全量数据源,替代不可靠的currentList

    • 在adapter类中声明私有全量列表缓存,避免跨线程访问异常:
      private var fullList: List<Articolo> = emptyList()
    • 重写父类的submitList方法,在数据提交时同步更新全量缓存,不直接依赖父类的内部存储:
      override fun submitList(list: List<Articolo>?) {
          fullList = list ?: emptyList()
          super.submitList(fullList)
      }
      
    • 对外暴露单独的全量数据更新方法,给LiveData回调逻辑使用:
      fun updateFullData(newData: List<Articolo>) {
          fullList = newData
      }
      
  2. 实现符合Kotlin空安全规则的自定义Filter

    • 所有过滤逻辑基于独立维护的fullList执行,从根源上避免空值问题:
      private val itemFilter = object : Filter() {
          // performFiltering在子线程执行,所有数据匹配逻辑都放在这里
          override fun performFiltering(constraint: CharSequence?): FilterResults {
              val query = constraint?.toString()?.trim().orEmpty()
              // 关键词为空时直接返回全量列表
              val filteredResult = if (query.isEmpty()) {
                  fullList
              } else {
                  // 匹配条码、描述字段,过滤时生成新的列表实例,不修改原列表
                  fullList.filter {
                      it.barcode.contains(query, ignoreCase = true)
                      || it.description.contains(query, ignoreCase = true)
                  }
              }
              return FilterResults().apply {
                  values = filteredResult
                  count = filteredResult.size
              }
          }
      
          // publishResults切回主线程执行,处理UI刷新逻辑
          override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
              // 做空值兜底,避免强转类型异常
              val finalList = (results?.values as? List<Articolo>) ?: emptyList()
              // 传入全新的ArrayList实例,解决引用校验导致的刷新失效问题
              submitList(ArrayList(finalList))
          }
      }
      
    • 重写getFilter方法对外暴露这个Filter实例:
      override fun getFilter(): Filter = itemFilter
      

    为什么一定要传全新的ArrayList实例?因为ListAdapter内部的DiffUtil校验逻辑,如果检测到传入的列表和当前adapter持有的列表是同一个内存引用,会直接判定内容无变化,跳过整个差异计算和刷新流程,这也是很多开发者过滤后列表毫无反应的核心原因。

  3. 解决和LiveData绑定的逻辑冲突

    • 不要在LiveData观察回调里直接把全量数据传给submitList,而是先更新adapter里的全量缓存,再用当前的搜索关键词重新触发一次过滤,避免数据更新覆盖之前的过滤结果:
      // Fragment/Activity里的LiveData观察逻辑
      viewModel.articoliList.observe(viewLifecycleOwner) { fullData ->
          adapter.updateFullData(fullData)
          // 读取当前SearchView的实时输入值,重新触发过滤
          adapter.filter.filter(searchView.query)
      }
      
  4. SearchView监听逻辑做最小化处理

    • 不要在搜索监听里手动操作列表,直接把输入的关键词透传给adapter的filter即可:
      searchView.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
          }
      })
      
高频踩坑提醒
  • 不要在performFiltering里调用任何和UI相关的方法,包括submitList,这个方法运行在子线程,所有列表更新操作必须放在主线程的publishResults里执行
  • 永远不要修改fullList的内容,这个列表的作用是存储从数据库拉取的原始全量数据,所有过滤结果都是基于它生成的临时列表,不要把过滤后的结果反向赋值给fullList
  • 不要为了强制刷新调用notifyDataSetChanged,只要给submitList传入全新的列表实例,ListAdapter的DiffUtil会自动计算item差异,做高效的局部刷新

内容的提问来源于stack exchange,提问作者NiceToMytyuk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.07 04:24:04