基于ViewModel LiveData的RecyclerView添加SearchView过滤失败求助
解决ViewModel+LiveData下RecyclerView的SearchView过滤问题
看起来你遇到的核心问题是Adapter里的数据源逻辑搞反了,再加上ViewModel的LiveData更新会重置数据,导致Filterable接口的过滤效果完全没体现出来。我帮你梳理下问题点,然后给出完整的修正方案:
问题根源分析
- 你的
getItemCount()和onBindViewHolder()里用的都是原始数据源mNetworkEntries,而过滤后的mFilteredNetworkEntries根本没被用来展示数据——这就导致不管过滤逻辑怎么写,界面显示的还是原始列表,自然看不到过滤效果。 setNetworks()方法里用mFilteredNetworkEntries.addAll(mNetworkEntries)会导致重复添加数据,当ViewModel的LiveData更新时,原始数据被替换,但过滤列表会累积旧数据,出现混乱。- 没有正确关联SearchView和Adapter的过滤逻辑,或者LiveData更新时没有保留当前的搜索状态。
修正方案
第一步:修复Adapter的数据源逻辑
把Adapter里用来展示的数据源换成mFilteredNetworkEntries,原始数据mNetworkEntries只用来保存完整的列表,作为过滤的基准。修改后的关键代码如下:
public class NetworkAdapter extends RecyclerView.Adapter<NetworkAdapter.NetworksViewHolder> implements Filterable { // 成员变量保持不变,注意mFilteredNetworkEntries初始化空列表 private List<NetworkEntry> mNetworkEntries; private List<NetworkEntry> mFilteredNetworkEntries = new ArrayList<>(); private Context mContext; private ItemClickListener mItemClickListener; // 构造函数不变 public NetworkAdapter(Context context, ItemClickListener listener) { mContext = context; mItemClickListener = listener; } @Override public NetworksViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext) .inflate(R.layout.network_list_item, parent, false); return new NetworksViewHolder(view); } @Override public void onBindViewHolder(NetworksViewHolder holder, int position) { holder.onBindData(position); } // 1. 修改getItemCount,返回过滤后的列表长度 @Override public int getItemCount() { return mFilteredNetworkEntries.size(); } // 2. 修改setNetworks方法,正确初始化原始数据和过滤列表 public void setNetworks(List<NetworkEntry> networkEntries) { mNetworkEntries = new ArrayList<>(networkEntries); // 保存原始数据副本 mFilteredNetworkEntries.clear(); mFilteredNetworkEntries.addAll(networkEntries); // 初始化过滤列表为完整数据 notifyDataSetChanged(); } // 3. 修改ViewHolder的onBindData,从过滤列表取数据 public class NetworksViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { ImageView networkLogoImageView; TextView networkNameTextView; TextView joinTextView; TextView commissionTextView; TextView minPayTextView; TextView payFrequencyTextView; TextView offerTextView; TextView likeTextView; View containerView; public NetworksViewHolder(View itemView) { super(itemView); // 控件初始化逻辑不变 networkLogoImageView = itemView.findViewById(R.id.iv_logo); networkNameTextView = itemView.findViewById(R.id.tv_network_name); joinTextView = itemView.findViewById(R.id.bt_join); commissionTextView = itemView.findViewById(R.id.iv_commission); minPayTextView = itemView.findViewById(R.id.tv_min_pay); payFrequencyTextView = itemView.findViewById(R.id.tv_pay_frequency); offerTextView = itemView.findViewById(R.id.iv_offers); likeTextView = itemView.findViewById(R.id.iv_likes); containerView = itemView.findViewById(R.id.layoutContainer); joinTextView.setOnClickListener(this); containerView.setOnClickListener(this); } public void onBindData(int position) { // 关键:从过滤后的列表获取数据 NetworkEntry networkEntry = mFilteredNetworkEntries.get(getAdapterPosition()); Picasso.with(mContext).load(networkEntry.getNetworkImageUrl()).into(networkLogoImageView); networkNameTextView.setText(networkEntry.getNetworkName()); commissionTextView.setText(networkEntry.getNetworkCommission()); minPayTextView.setText(mContext.getString(R.string.tv_min_pay, networkEntry.getNetworkMinPay())); payFrequencyTextView.setText(networkEntry.getNetworkPayFrequency()); // 补全剩余控件的绑定逻辑 offerTextView.setText(networkEntry.getNetworkOffers()); likeTextView.setText(networkEntry.getNetworkLikes()); } @Override public void onClick(View v) { // 点击逻辑不变,注意从过滤列表取数据 NetworkEntry entry = mFilteredNetworkEntries.get(getAdapterPosition()); mItemClickListener.onItemClickListener(entry.getNetworkData()); } } // 4. 优化Filter逻辑,避免空指针 @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence charSequence) { String searchQuery = charSequence.toString().trim().toLowerCase(); List<NetworkEntry> filteredList = new ArrayList<>(); if (TextUtils.isEmpty(searchQuery) || mNetworkEntries == null) { filteredList.addAll(mNetworkEntries != null ? mNetworkEntries : Collections.emptyList()); } else { for (NetworkEntry entry : mNetworkEntries) { // 这里可以扩展多字段过滤,比如同时匹配佣金、支付频率等 if (entry.getNetworkName().toLowerCase().contains(searchQuery)) { filteredList.add(entry); } } } FilterResults results = new FilterResults(); results.values = filteredList; return results; } @Override protected void publishResults(CharSequence charSequence, FilterResults filterResults) { mFilteredNetworkEntries = (List<NetworkEntry>) filterResults.values; notifyDataSetChanged(); } }; } public interface ItemClickListener { void onItemClickListener(NetworkData networkData); } }
第二步:在Activity/Fragment中关联SearchView和ViewModel
在你的界面类里,需要同时处理LiveData的数据更新和SearchView的过滤触发,还要保证LiveData更新时保留当前的搜索状态:
public class NetworkListFragment extends Fragment { private NetworkAdapter mAdapter; private NetworkViewModel mViewModel; private SearchView mSearchView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_network_list, container, false); // 初始化RecyclerView RecyclerView recyclerView = view.findViewById(R.id.recycler_view_networks); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); mAdapter = new NetworkAdapter(getContext(), this::onNetworkItemClick); recyclerView.setAdapter(mAdapter); // 初始化SearchView mSearchView = view.findViewById(R.id.search_view_networks); mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { mAdapter.getFilter().filter(query); return true; } @Override public boolean onQueryTextChange(String newText) { mAdapter.getFilter().filter(newText); return true; } }); // 初始化ViewModel并观察LiveData mViewModel = new ViewModelProvider(this).get(NetworkViewModel.class); mViewModel.getNetworkEntries().observe(getViewLifecycleOwner(), networkEntries -> { mAdapter.setNetworks(networkEntries); // 如果当前有搜索关键词,更新数据后重新过滤 String currentQuery = mSearchView.getQuery().toString(); if (!TextUtils.isEmpty(currentQuery)) { mAdapter.getFilter().filter(currentQuery); } }); return view; } private void onNetworkItemClick(NetworkData networkData) { // 处理条目点击逻辑 } }
额外注意事项
- 确保
NetworkEntry类是不可变的,或者在ViewModel中返回新的列表实例,这样LiveData才能正确触发观察者回调。 - 如果数据量很大,过滤逻辑可以进一步优化,比如使用流式API或者提前缓存索引。
- Filterable接口的
performFiltering()方法本身是在后台线程执行的,不用担心阻塞主线程。
内容的提问来源于stack exchange,提问作者Kanchan
相关产品推荐
相关产品推荐

