如何使用 SearchView 过滤 RecyclerView

Posted

技术标签:

【中文标题】如何使用 SearchView 过滤 RecyclerView【英文标题】:How to filter a RecyclerView with a SearchView 【发布时间】:2015-08-04 13:37:48 【问题描述】:

我正在尝试从支持库中实现SearchView。我希望用户使用SearchView 过滤List 中的电影RecyclerView

到目前为止,我已经学习了一些教程,并将SearchView 添加到ActionBar,但我不确定从这里去哪里。我看过一些示例,但没有一个在您开始输入时显示结果。

这是我的MainActivity

public class MainActivity extends ActionBarActivity 

    RecyclerView mRecyclerView;
    RecyclerView.LayoutManager mLayoutManager;
    RecyclerView.Adapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setHasFixedSize(true);

        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);

        mAdapter = new CardAdapter() 
            @Override
            public Filter getFilter() 
                return null;
            
        ;
        mRecyclerView.setAdapter(mAdapter);
    

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        return true;
    

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in androidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) 
            return true;
        

        return super.onOptionsItemSelected(item);
    

这是我的Adapter

public abstract class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> implements Filterable 

    List<Movie> mItems;

    public CardAdapter() 
        super();
        mItems = new ArrayList<Movie>();
        Movie movie = new Movie();
        movie.setName("Spiderman");
        movie.setRating("92");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Doom 3");
        movie.setRating("91");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers");
        movie.setRating("88");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers 2");
        movie.setRating("87");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers 3");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Noah");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman 2");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman 3");
        movie.setRating("86");
        mItems.add(movie);
    

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) 
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);
        return new ViewHolder(v);
    

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) 
        Movie movie = mItems.get(i);
        viewHolder.tvMovie.setText(movie.getName());
        viewHolder.tvMovieRating.setText(movie.getRating());
    

    @Override
    public int getItemCount() 
        return mItems.size();
    

    class ViewHolder extends RecyclerView.ViewHolder

        public TextView tvMovie;
        public TextView tvMovieRating;

        public ViewHolder(View itemView) 
            super(itemView);
            tvMovie = (TextView)itemView.findViewById(R.id.movieName);
            tvMovieRating = (TextView)itemView.findViewById(R.id.movieRating);
        
    

【问题讨论】:

【参考方案1】:

简介

由于您的问题并不清楚您到底遇到了什么问题,所以我写了这个关于如何实现此功能的快速演练;如果您仍有疑问,请随时提出。

我在GitHub Repository 中提供了我在这里谈论的所有内容的工作示例。

无论如何,结果应该是这样的:

如果您首先想试用演示应用,可以从 Play 商店安装它:

不管怎样,让我们​​开始吧。


设置SearchView

在文件夹res/menu 中创建一个名为main_menu.xml 的新文件。在其中添加一个项目并将actionViewClass 设置为android.support.v7.widget.SearchView。由于您使用的是支持库,因此您必须使用支持库的命名空间来设置 actionViewClass 属性。您的 xml 文件应如下所示:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/action_search"
          android:title="@string/action_search"
          app:actionViewClass="android.support.v7.widget.SearchView"
          app:showAsAction="always"/>
      
</menu>

在您的FragmentActivity 中,您必须像往常一样扩充此菜单xml,然后您可以查找包含SearchViewMenuItem 并实现我们将使用的OnQueryTextListener监听输入到SearchView的文本的变化:

@Override
public boolean onCreateOptionsMenu(Menu menu) 
    getMenuInflater().inflate(R.menu.menu_main, menu);

    final MenuItem searchItem = menu.findItem(R.id.action_search);
    final SearchView searchView = (SearchView) searchItem.getActionView();
    searchView.setOnQueryTextListener(this);

    return true;


@Override
public boolean onQueryTextChange(String query) 
    // Here is where we are going to implement the filter logic
    return false;


@Override
public boolean onQueryTextSubmit(String query) 
    return false;

现在SearchView 可以使用了。完成Adapter 的实现后,我们将在稍后的onQueryTextChange() 中实现过滤器逻辑。


设置Adapter

首先,这是我将用于此示例的模型类:

public class ExampleModel 

    private final long mId;
    private final String mText;

    public ExampleModel(long id, String text) 
        mId = id;
        mText = text;
    

    public long getId() 
        return mId;
    

    public String getText() 
        return mText;
    

这只是您的基本模型,它将在RecyclerView 中显示文本。这是我将用来显示文本的布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="model"
            type="com.github.wrdlbrnft.searchablerecyclerviewdemo.ui.models.ExampleModel"/>

    </data>

    <FrameLayout
        android:layout_
        android:layout_
        android:background="?attr/selectableItemBackground"
        android:clickable="true">

        <TextView
            android:layout_
            android:layout_
            android:padding="8dp"
            android:text="@model.text"/>

    </FrameLayout>

</layout>

如您所见,我使用数据绑定。如果您以前从未使用过数据绑定,请不要气馁!它非常简单而强大,但是我无法解释它在这个答案的范围内是如何工作的。

这是ExampleModel 类的ViewHolder

public class ExampleViewHolder extends RecyclerView.ViewHolder 

    private final ItemExampleBinding mBinding;

    public ExampleViewHolder(ItemExampleBinding binding) 
        super(binding.getRoot());
        mBinding = binding;
    

    public void bind(ExampleModel item) 
        mBinding.setModel(item);
    

同样没有什么特别的。它只是使用数据绑定将模型类绑定到这个布局,就像我们在上面的布局 xml 中定义的那样。

现在我们终于可以进入真正有趣的部分了:编写适配器。我将跳过Adapter 的基本实现,而是专注于与此答案相关的部分。

但首先我们要谈一件事:SortedList 类。


排序列表

SortedList 是一个非常棒的工具,它是RecyclerView 库的一部分。它负责通知Adapter 关于数据集的更改,这是一种非常有效的方式。它唯一需要您做的就是指定元素的顺序。您需要通过实现一个compare() 方法来做到这一点,该方法比较SortedList 中的两个元素,就像Comparator 一样。但不是对List 进行排序,而是用于对RecyclerView 中的项目进行排序!

SortedList 通过您必须实现的 Callback 类与 Adapter 交互:

private final SortedList.Callback<ExampleModel> mCallback = new SortedList.Callback<ExampleModel>() 

    @Override
    public void onInserted(int position, int count) 
         mAdapter.notifyItemRangeInserted(position, count);
    

    @Override
    public void onRemoved(int position, int count) 
        mAdapter.notifyItemRangeRemoved(position, count);
    

    @Override
    public void onMoved(int fromPosition, int toPosition) 
        mAdapter.notifyItemMoved(fromPosition, toPosition);
    

    @Override
    public void onChanged(int position, int count) 
        mAdapter.notifyItemRangeChanged(position, count);
    

    @Override
    public int compare(ExampleModel a, ExampleModel b) 
        return mComparator.compare(a, b);
    

    @Override
    public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) 
        return oldItem.equals(newItem);
    

    @Override
    public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) 
        return item1.getId() == item2.getId();
    

在回调顶部的方法中,如onMovedonInserted 等,您必须调用您的Adapter 的等效通知方法。底部compareareContentsTheSameareItemsTheSame这三个方法,你要根据你想显示什么样的对象以及这些对象在屏幕上出现的顺序来实现。

让我们一一介绍这些方法:

@Override
public int compare(ExampleModel a, ExampleModel b) 
    return mComparator.compare(a, b);

这就是我之前讲的compare()方法。在此示例中,我只是将调用传递给比较两个模型的Comparator。如果您希望项目按字母顺序显示在屏幕上。此比较器可能如下所示:

private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() 
    @Override
    public int compare(ExampleModel a, ExampleModel b) 
        return a.getText().compareTo(b.getText());
    
;

现在我们来看看下一个方法:

@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) 
    return oldItem.equals(newItem);

此方法的目的是确定模型的内容是否已更改。 SortedList 使用它来确定是否需要调用更改事件 - 换句话说,RecyclerView 是否应该交叉淡入淡出旧版本和新版本。如果您的模型类具有正确的 equals()hashCode() 实现,您通常可以像上面那样实现它。如果我们将 equals()hashCode() 实现添加到 ExampleModel 类,它应该看起来像这样:

public class ExampleModel implements SortedListAdapter.ViewModel 

    private final long mId;
    private final String mText;

    public ExampleModel(long id, String text) 
        mId = id;
        mText = text;
    

    public long getId() 
        return mId;
    

    public String getText() 
        return mText;
    

    @Override
    public boolean equals(Object o) 
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ExampleModel model = (ExampleModel) o;

        if (mId != model.mId) return false;
        return mText != null ? mText.equals(model.mText) : model.mText == null;

    

    @Override
    public int hashCode() 
        int result = (int) (mId ^ (mId >>> 32));
        result = 31 * result + (mText != null ? mText.hashCode() : 0);
        return result;
    

附注:大多数 IDE,如 Android Studio、IntelliJ 和 Eclipse 都具有为您生成 equals()hashCode() 实现的功能,只需按一下按钮!所以你不必自己实现它们。在 Internet 上查找它在 IDE 中的工作原理!

现在我们来看看最后一种方法:

@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) 
    return item1.getId() == item2.getId();

SortedList 使用此方法来检查两个项目是否引用同一事物。简单来说(不解释SortedList 的工作原理),它用于确定对象是否已包含在List 中,以及是否需要播放添加、移动或更改动画。如果您的模型有 id,您通常会在此方法中只比较 id。如果他们不这样做,您需要找出其他方法来检查这一点,但是您最终实现这取决于您的特定应用程序。通常,给所有模型一个 id 是最简单的选择 - 例如,如果您从数据库中查询数据,它可能是主键字段。

正确实现SortedList.Callback 后,我们可以创建SortedList 的实例:

final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);

作为SortedList 构造函数中的第一个参数,您需要传递模型的类。另一个参数就是我们上面定义的SortedList.Callback

现在让我们开始谈正事:如果我们用SortedList 实现Adapter,它应该看起来像这样:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> 

    private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() 
        @Override
        public int compare(ExampleModel a, ExampleModel b) 
            return mComparator.compare(a, b);
        

        @Override
        public void onInserted(int position, int count) 
            notifyItemRangeInserted(position, count);
        

        @Override
        public void onRemoved(int position, int count) 
            notifyItemRangeRemoved(position, count);
        

        @Override
        public void onMoved(int fromPosition, int toPosition) 
            notifyItemMoved(fromPosition, toPosition);
        

        @Override
        public void onChanged(int position, int count) 
            notifyItemRangeChanged(position, count);
        

        @Override
        public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) 
            return oldItem.equals(newItem);
        

        @Override
        public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) 
            return item1.getId() == item2.getId();
        
    );

    private final LayoutInflater mInflater;
    private final Comparator<ExampleModel> mComparator;

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) 
        mInflater = LayoutInflater.from(context);
        mComparator = comparator;
    

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
        final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
        return new ExampleViewHolder(binding);
    

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) 
        final ExampleModel model = mSortedList.get(position);
        holder.bind(model);
    

    @Override
    public int getItemCount() 
        return mSortedList.size();
    

用于对项目进行排序的Comparator 是通过构造函数传入的,因此即使项目应该以不同的顺序显示,我们也可以使用相同的Adapter

现在我们差不多完成了!但我们首先需要一种向Adapter 添加或删除项目的方法。为此,我们可以向Adapter 添加方法,允许我们向SortedList 添加和删除项目:

public void add(ExampleModel model) 
    mSortedList.add(model);


public void remove(ExampleModel model) 
    mSortedList.remove(model);


public void add(List<ExampleModel> models) 
    mSortedList.addAll(models);


public void remove(List<ExampleModel> models) 
    mSortedList.beginBatchedUpdates();
    for (ExampleModel model : models) 
        mSortedList.remove(model);
    
    mSortedList.endBatchedUpdates();

我们不需要在这里调用任何通知方法,因为SortedList 已经通过SortedList.Callback 执行此操作!除此之外,这些方法的实现非常简单,只有一个例外:remove 方法删除了List 的模型。由于SortedList 只有一个可以删除单个对象的 remove 方法,我们需要遍历列表并逐个删除模型。在开始时调用beginBatchedUpdates() 会批量处理我们将对SortedList 所做的所有更改并提高性能。当我们调用endBatchedUpdates() 时,RecyclerView 会立即收到所有更改的通知。

此外,您必须了解的是,如果您将对象添加到 SortedList 并且它已经在 SortedList 中,则不会再次添加它。相反,SortedList 使用 areContentsTheSame() 方法来确定对象是否已更改 - 如果它具有 RecyclerView 中的项目将被更新。

无论如何,我通常更喜欢一种方法,它允许我一次替换RecyclerView 中的所有项目。删除List 中没有的所有内容并添加SortedList 中缺少的所有项目:

public void replaceAll(List<ExampleModel> models) 
    mSortedList.beginBatchedUpdates();
    for (int i = mSortedList.size() - 1; i >= 0; i--) 
        final ExampleModel model = mSortedList.get(i);
        if (!models.contains(model)) 
            mSortedList.remove(model);
        
    
    mSortedList.addAll(models);
    mSortedList.endBatchedUpdates();

此方法再次将所有更新批处理在一起以提高性能。第一个循环是相反的,因为在开始时删除一个项目会弄乱它之后出现的所有项目的索引,这在某些情况下会导致数据不一致等问题。之后,我们只需使用addAll()List 添加到SortedList 以添加所有不在SortedList 中的项目,并且 - 就像我上面描述的那样 - 更新所有已经在SortedList 中的项目但已经改变了。

这样Adapter 就完成了。整个事情应该是这样的:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> 

    private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() 
        @Override
        public int compare(ExampleModel a, ExampleModel b) 
            return mComparator.compare(a, b);
        

        @Override
        public void onInserted(int position, int count) 
            notifyItemRangeInserted(position, count);
        

        @Override
        public void onRemoved(int position, int count) 
            notifyItemRangeRemoved(position, count);
        

        @Override
        public void onMoved(int fromPosition, int toPosition) 
            notifyItemMoved(fromPosition, toPosition);
        

        @Override
        public void onChanged(int position, int count) 
            notifyItemRangeChanged(position, count);
        

        @Override
        public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) 
            return oldItem.equals(newItem);
        

        @Override
        public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) 
            return item1 == item2;
        
    );

    private final Comparator<ExampleModel> mComparator;
    private final LayoutInflater mInflater;

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) 
        mInflater = LayoutInflater.from(context);
        mComparator = comparator;
    

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
        final ItemExampleBinding binding = ItemExampleBinding.inflate(mInflater, parent, false);
        return new ExampleViewHolder(binding);
    

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) 
        final ExampleModel model = mSortedList.get(position);
        holder.bind(model);
    

    public void add(ExampleModel model) 
        mSortedList.add(model);
    

    public void remove(ExampleModel model) 
        mSortedList.remove(model);
    

    public void add(List<ExampleModel> models) 
        mSortedList.addAll(models);
    

    public void remove(List<ExampleModel> models) 
        mSortedList.beginBatchedUpdates();
        for (ExampleModel model : models) 
            mSortedList.remove(model);
        
        mSortedList.endBatchedUpdates();
    

    public void replaceAll(List<ExampleModel> models) 
        mSortedList.beginBatchedUpdates();
        for (int i = mSortedList.size() - 1; i >= 0; i--) 
            final ExampleModel model = mSortedList.get(i);
            if (!models.contains(model)) 
                mSortedList.remove(model);
            
        
        mSortedList.addAll(models);
        mSortedList.endBatchedUpdates();
    

    @Override
    public int getItemCount() 
        return mSortedList.size();
    

现在唯一缺少的就是实现过滤!


实现过滤逻辑

要实现过滤器逻辑,我们首先必须定义所有可能模型的List。对于这个例子,我从一组电影中创建了一个 ListExampleModel 实例:

private static final String[] MOVIES = new String[]
        ...
;

private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() 
    @Override
    public int compare(ExampleModel a, ExampleModel b) 
        return a.getText().compareTo(b.getText());
    
;

private ExampleAdapter mAdapter;
private List<ExampleModel> mModels;
private RecyclerView mRecyclerView;

    @Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    mAdapter = new ExampleAdapter(this, ALPHABETICAL_COMPARATOR);

    mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
    mBinding.recyclerView.setAdapter(mAdapter);

    mModels = new ArrayList<>();
    for (String movie : MOVIES) 
        mModels.add(new ExampleModel(movie));
    
    mAdapter.add(mModels);

这里没有什么特别的,我们只是实例化Adapter并将其设置为RecyclerView。之后,我们从 MOVIES 数组中的电影名称创建模型 List。然后我们将所有模型添加到SortedList

现在我们可以回到我们之前定义的onQueryTextChange() 并开始实现过滤器逻辑:

@Override
public boolean onQueryTextChange(String query) 
    final List<ExampleModel> filteredModelList = filter(mModels, query);
    mAdapter.replaceAll(filteredModelList);
    mBinding.recyclerView.scrollToPosition(0);
    return true;

这又很简单。我们调用方法filter() 并传入ExampleModels 的List 以及查询字符串。然后我们在Adapter 上调用replaceAll() 并传入filter() 返回的过滤后的List。我们还必须在RecyclerView 上调用scrollToPosition(0),以确保用户在搜索某些内容时始终可以看到所有项目。否则RecyclerView 可能会在过滤时停留在向下滚动的位置,然后隐藏一些项目。滚动到顶部可确保在搜索时获得更好的用户体验。

现在唯一要做的就是自己实现filter()

private static List<ExampleModel> filter(List<ExampleModel> models, String query) 
    final String lowerCaseQuery = query.toLowerCase();

    final List<ExampleModel> filteredModelList = new ArrayList<>();
    for (ExampleModel model : models) 
        final String text = model.getText().toLowerCase();
        if (text.contains(lowerCaseQuery)) 
            filteredModelList.add(model);
        
    
    return filteredModelList;

我们在这里做的第一件事是在查询字符串上调用toLowerCase()。我们不希望我们的搜索函数区分大小写,通过在我们比较的所有字符串上调用toLowerCase(),我们可以确保无论大小写如何都返回相同的结果。然后它只是遍历我们传递给它的List 中的所有模型,并检查查询字符串是否包含在模型的文本中。如果是,则将模型添加到过滤后的List

就是这样!上述代码将在 API 级别 7 及更高级别上运行,从 API 级别 11 开始,您可以免费获得项目动画!

我意识到这是一个非常详细的描述,这可能会使整个事情看起来比实际更复杂,但是有一种方法可以概括整个问题并基于 SortedList 实现 Adapter简单得多。


概括问题并简化Adapter

在本节中,我不会详细介绍 - 部分原因是我在 Stack Overflow 上遇到了字符数限制,但也因为上面已经解释了大部分内容 - 但总结一下变化:我们可以实现一个基本的Adapter 类,它已经负责处理SortedList 以及将模型绑定到ViewHolder 实例,并提供了一种基于SortedList 实现Adapter 的便捷方法。为此,我们必须做两件事:

我们需要创建一个所有模型类都必须实现的ViewModel 接口 我们需要创建一个ViewHolder 子类,它定义了一个bind() 方法,Adapter 可以用来自动绑定模型。

这允许我们只关注应该在RecyclerView 中显示的内容,只需实现模型和相应的ViewHolder 实现即可。使用这个基类,我们不必担心Adapter 及其SortedList 的复杂细节。

排序列表适配器

由于 *** 上答案的字符数限制,我无法完成实现此基类的每一步,甚至无法在此处添加完整的源代码,但您可以找到此基类的完整源代码 - 我称之为SortedListAdapter - 在这个GitHub Gist.

为了让您的生活更简单,我在 jCenter 上发布了一个库,其中包含 SortedListAdapter!如果您想使用它,那么您需要做的就是将此依赖项添加到您应用的 build.gradle 文件中:

compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'

你可以在on the library homepage找到更多关于这个库的信息。

使用 SortedListAdapter

要使用SortedListAdapter,我们必须进行两项更改:

更改ViewHolder,使其扩展SortedListAdapter.ViewHolder。类型参数应该是应该绑定到这个ViewHolder的模型——在这种情况下是ExampleModel。您必须将数据绑定到 performBind() 而不是 bind() 中的模型。

 public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> 

     private final ItemExampleBinding mBinding;

     public ExampleViewHolder(ItemExampleBinding binding) 
         super(binding.getRoot());
         mBinding = binding;
     

     @Override
     protected void performBind(ExampleModel item) 
         mBinding.setModel(item);
     
 

确保所有模型都实现ViewModel 接口:

 public class ExampleModel implements SortedListAdapter.ViewModel 
     ...
 

之后,我们只需更新ExampleAdapter 以扩展SortedListAdapter 并删除我们不再需要的所有内容。 type 参数应该是您正在使用的模型的类型 - 在本例中为 ExampleModel。但如果您使用不同类型的模型,请将 type 参数设置为 ViewModel

public class ExampleAdapter extends SortedListAdapter<ExampleModel> 

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) 
        super(context, ExampleModel.class, comparator);
    

    @Override
    protected ViewHolder<? extends ExampleModel> onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) 
        final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
        return new ExampleViewHolder(binding);
    

    @Override
    protected boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) 
        return item1.getId() == item2.getId();
    

    @Override
    protected boolean areItemContentsTheSame(ExampleModel oldItem, ExampleModel newItem) 
        return oldItem.equals(newItem);
    

之后我们就完成了!然而最后一件事要提到:SortedListAdapter 没有我们原来的ExampleAdapter 拥有的相同的add()remove()replaceAll() 方法。它使用单独的Editor 对象来修改可以通过edit() 方法访问的列表中的项目。因此,如果您想删除或添加项目,您必须调用edit(),然后在此Editor 实例上添加和删除项目,完成后,调用commit() 以将更改应用于SortedList

mAdapter.edit()
        .remove(modelToRemove)
        .add(listOfModelsToAdd)
        .commit();

您以这种方式进行的所有更改都被批量处理以提高性能。我们在上面章节中实现的replaceAll() 方法也存在于这个Editor 对象上:

mAdapter.edit()
        .replaceAll(mModels)
        .commit();

如果您忘记致电commit(),则您的任何更改都不会应用!

【讨论】:

@TiagoOliveira 好吧,它开箱即用:D 数据绑定对于不熟悉它的人来说是一个障碍,但我还是把它包括在内,因为它很棒,我想推广它。由于某种原因,似乎没有多少人知道它...... 我还没有阅读完整的答案,我不得不暂停阅读一半来写这篇评论——这是我在这里找到的最好的答案之一!谢谢! 我只是喜欢你的样子:“从你的问题中不清楚你遇到了什么问题,所以这是我刚刚做的一个完整的例子”:D +1 只是为了向我们展示 Android 中存在数据绑定!我从来没有听说过,似乎我会开始使用它。谢谢 这个解决方案冗长得离谱,而且一般来说过度设计。去第二个。【参考方案2】:

你需要做的就是在RecyclerView.Adapter中添加filter方法:

public void filter(String text) 
    items.clear();
    if(text.isEmpty())
        items.addAll(itemsCopy);
     else
        text = text.toLowerCase();
        for(PhoneBookItem item: itemsCopy)
            if(item.name.toLowerCase().contains(text) || item.phone.toLowerCase().contains(text))
                items.add(item);
            
        
    
    notifyDataSetChanged();

itemsCopy 在适配器的构造函数中初始化,如itemsCopy.addAll(items)

如果您这样做,只需从OnQueryTextListener 拨打filter

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() 
    @Override
    public boolean onQueryTextSubmit(String query) 
        adapter.filter(query);
        return true;
    

    @Override
    public boolean onQueryTextChange(String newText) 
        adapter.filter(newText);
        return true;
    
);

这是一个按姓名和电话号码过滤我的电话簿的示例。

【讨论】:

我认为这应该是公认的答案。它更简单,而且很有效 简单高效! 请注意,如果您采用这种方法而不是 @Xaver Kapeller 回答,则会丢失动画。 没有尝试接受的答案,因为它太长了。这个答案有效且易于实施。不要忘记在您的菜单项 XML 中添加“ app:actionViewClass="android.support.v7.widget.SearchView"。 什么是 items 和 itemsCopy here?【参考方案3】:

以更简洁的方式遵循@Shruthi Kamoji,我们可以只使用可过滤的,它的意思是:

public abstract class GenericRecycleAdapter<E> extends RecyclerView.Adapter implements Filterable

    protected List<E> list;
    protected List<E> originalList;
    protected Context context;

    public GenericRecycleAdapter(Context context,
    List<E> list)
    
        this.originalList = list;
        this.list = list;
        this.context = context;
    

    ...

    @Override
    public Filter getFilter() 
        return new Filter() 
            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) 
                list = (List<E>) results.values;
                notifyDataSetChanged();
            

            @Override
            protected FilterResults performFiltering(CharSequence constraint) 
                List<E> filteredResults = null;
                if (constraint.length() == 0) 
                    filteredResults = originalList;
                 else 
                    filteredResults = getFilteredResults(constraint.toString().toLowerCase());
                

                FilterResults results = new FilterResults();
                results.values = filteredResults;

                return results;
            
        ;
    

    protected List<E> getFilteredResults(String constraint) 
        List<E> results = new ArrayList<>();

        for (E item : originalList) 
            if (item.getName().toLowerCase().contains(constraint)) 
                results.add(item);
            
        
        return results;
    
 

这里的 E 是一个通用类型,你可以使用你的类来扩展它:

public class customerAdapter extends GenericRecycleAdapter<CustomerModel>

或者只是将 E 更改为您想要的类型(例如&lt;CustomerModel&gt;

然后从 searchView(你可以放在 menu.xml 上的小部件):

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() 
    @Override
    public boolean onQueryTextSubmit(String text) 
        return false;
    

    @Override
    public boolean onQueryTextChange(String text) 
        yourAdapter.getFilter().filter(text);
        return true;
    
);

【讨论】:

我用这样的东西!工作正常且通用的示例! 你好,谁能帮我一步一步来:***.com/questions/40754174/… 这比投票的答案要好得多,因为该操作是在 performFiltering 方法中的工作线程上完成的。 但是您将对同一个 List 的引用分配给不同的变量。例如 this.originalList = list;您应该改用 addAll 或在 ArrayList 构造函数中传递列表 想知道 item.getName() 如何在 getFilteredResults 方法中为通用 E 工作。【参考方案4】:

在适配器中:

public void setFilter(List<Channel> newList)
        mChannels = new ArrayList<>();
        mChannels.addAll(newList);
        notifyDataSetChanged();
    

活动中:

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() 
            @Override
            public boolean onQueryTextSubmit(String query) 
                return false;
            

            @Override
            public boolean onQueryTextChange(String newText) 
                newText = newText.toLowerCase();
                ArrayList<Channel> newList = new ArrayList<>();
                for (Channel channel: channels)
                    String channelName = channel.getmChannelName().toLowerCase();
                    if (channelName.contains(newText))
                        newList.add(channel);
                    
                
                mAdapter.setFilter(newList);
                return true;
            
        );

【讨论】:

【参考方案5】:

只需在适配器中创建两个列表,一个是原始的,一个是临时的,并实现可过滤

    @Override
    public Filter getFilter() 
        return new Filter() 
            @Override
            protected FilterResults performFiltering(CharSequence constraint) 
                final FilterResults oReturn = new FilterResults();
                final ArrayList<T> results = new ArrayList<>();
                if (origList == null)
                    origList = new ArrayList<>(itemList);
                if (constraint != null && constraint.length() > 0) 
                    if (origList != null && origList.size() > 0) 
                        for (final T cd : origList) 
                            if (cd.getAttributeToSearch().toLowerCase()
                                    .contains(constraint.toString().toLowerCase()))
                                results.add(cd);
                        
                    
                    oReturn.values = results;
                    oReturn.count = results.size();//newly Aded by ZA
                 else 
                    oReturn.values = origList;
                    oReturn.count = origList.size();//newly added by ZA
                
                return oReturn;
            

            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(final CharSequence constraint,
                                          FilterResults results) 
                itemList = new ArrayList<>((ArrayList<T>) results.values);
                // FIXME: 8/16/2017 implement Comparable with sort below
                ///Collections.sort(itemList);
                notifyDataSetChanged();
            
        ;
    

在哪里

public GenericBaseAdapter(Context mContext, List<T> itemList) 
        this.mContext = mContext;
        this.itemList = itemList;
        this.origList = itemList;
    

【讨论】:

不错的解决方案。我创建了两个列表并使用了一个简单的过滤方法。我似乎无法将项目的正确适配器位置传递给下一个活动。如果您对此提出任何想法或想法,我将不胜感激:***.com/questions/46027110/…【参考方案6】:

通过使用 LiveData Android 架构组件,这可以通过任何类型的 Adapter 轻松实现。您只需执行以下步骤:

1. 将您的数据设置为从 Room Database 返回为 LiveData,如下例所示:

@Dao
public interface CustomDAO

@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
    public LiveData<List<Word>> searchFor(String searchquery);

2. 创建一个 ViewModel 对象,通过连接您的 DAO 和您的 UI

public class CustomViewModel extends AndroidViewModel 

    private final AppDatabase mAppDatabase;

    public WordListViewModel(@NonNull Application application) 
        super(application);
        this.mAppDatabase = AppDatabase.getInstance(application.getApplicationContext());
    

    public LiveData<List<Word>> searchQuery(String query) 
        return mAppDatabase.mWordDAO().searchFor(query);
    


3. 通过 onQueryTextListener 传入查询,即时调用 ViewModel 中的数据,如下所示:

onCreateOptionsMenu里面设置你的监听器如下

searchView.setOnQueryTextListener(onQueryTextListener);

在 SearchActivity 类中的某处设置查询侦听器,如下所示

private android.support.v7.widget.SearchView.OnQueryTextListener onQueryTextListener =
            new android.support.v7.widget.SearchView.OnQueryTextListener() 
                @Override
                public boolean onQueryTextSubmit(String query) 
                    getResults(query);
                    return true;
                

                @Override
                public boolean onQueryTextChange(String newText) 
                    getResults(newText);
                    return true;
                

                private void getResults(String newText) 
                    String queryText = "%" + newText + "%";
                    mCustomViewModel.searchQuery(queryText).observe(
                            SearchResultsActivity.this, new Observer<List<Word>>() 
                                @Override
                                public void onChanged(@Nullable List<Word> words) 
                                    if (words == null) return;
                                    searchAdapter.submitList(words);
                                
                            );
                
            ;

注意:步骤 (1.) 和 (2.) 是标准的 AAC ViewModelDAO 实现,是唯一真正的“魔法” OnQueryTextListener 会随着查询文本的变化而动态更新列表的结果。

如果您需要对此事进行更多说明,请随时提出。 我希望这会有所帮助:)。

【讨论】:

【参考方案7】:

我不知道为什么每个人都使用同一个列表的 2 个副本来解决这个问题。这使用了太多 RAM...

为什么不直接隐藏未找到的元素,并将它们的index 存储在Set 中以便以后恢复它们?这要少得多的 RAM,尤其是在您的对象非常大的情况下。

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.SampleViewHolders>
    private List<MyObject> myObjectsList; //holds the items of type MyObject
    private Set<Integer> foundObjects; //holds the indices of the found items

    public MyRecyclerViewAdapter(Context context, List<MyObject> myObjectsList)
    
        this.myObjectsList = myObjectsList;
        this.foundObjects = new HashSet<>();
        //first, add all indices to the indices set
        for(int i = 0; i < this.myObjectsList.size(); i++)
        
            this.foundObjects.add(i);
        
    

    @NonNull
    @Override
    public SampleViewHolders onCreateViewHolder(@NonNull ViewGroup parent, int viewType) 
        View layoutView = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.my_layout_for_staggered_grid, null);
        MyRecyclerViewAdapter.SampleViewHolders rcv = new MyRecyclerViewAdapter.SampleViewHolders(layoutView);
        return rcv;
    

    @Override
    public void onBindViewHolder(@NonNull SampleViewHolders holder, int position)
    
        //look for object in O(1) in the indices set
        if(!foundObjects.contains(position))
        
            //object not found => hide it.
            holder.hideLayout();
            return;
        
        else
        
            //object found => show it.
            holder.showLayout();
        

        //holder.imgImageView.setImageResource(...)
        //holder.nameTextView.setText(...)
    

    @Override
    public int getItemCount() 
        return myObjectsList.size();
    

    public void findObject(String text)
    
        //look for "text" in the objects list
        for(int i = 0; i < myObjectsList.size(); i++)
        
            //if it's empty text, we want all objects, so just add it to the set.
            if(text.length() == 0)
            
                foundObjects.add(i);
            
            else
            
                //otherwise check if it meets your search criteria and add it or remove it accordingly
                if (myObjectsList.get(i).getName().toLowerCase().contains(text.toLowerCase()))
                
                    foundObjects.add(i);
                
                else
                
                    foundObjects.remove(i);
                
            
        
        notifyDataSetChanged();
    

    public class SampleViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener
    
        public ImageView imgImageView;
        public TextView nameTextView;

        private final CardView layout;
        private final CardView.LayoutParams hiddenLayoutParams;
        private final CardView.LayoutParams shownLayoutParams;

        
        public SampleViewHolders(View itemView)
        
            super(itemView);
            itemView.setOnClickListener(this);
            imgImageView = (ImageView) itemView.findViewById(R.id.some_image_view);
            nameTextView = (TextView) itemView.findViewById(R.id.display_name_textview);

            layout = itemView.findViewById(R.id.card_view); //card_view is the id of my androidx.cardview.widget.CardView in my xml layout
            //prepare hidden layout params with height = 0, and visible layout params for later - see hideLayout() and showLayout()
            hiddenLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            hiddenLayoutParams.height = 0;
            shownLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        

        @Override
        public void onClick(View view)
        
            //implement...
        

        private void hideLayout() 
            //hide the layout
            layout.setLayoutParams(hiddenLayoutParams);
        

        private void showLayout() 
            //show the layout
            layout.setLayoutParams(shownLayoutParams);
        
    

我只是将EditText 作为我的搜索框:

cardsSearchTextView.addTextChangedListener(new TextWatcher() 
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) 

            

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) 

            

            @Override
            public void afterTextChanged(Editable editable) 
                myViewAdapter.findObject(editable.toString().toLowerCase());
            
        );

结果:

【讨论】:

我只是试一试,当我有一个包含 2000 多个项目的列表时,会出现延迟,因为它在主列表上搜索,而不是在过滤后的列表上搜索。但无论如何,谢谢你,当我有小清单时我会使用;) 哦,谢谢你给它一个机会!我没有尝试过这么大的列表,你可能是对的。 它并没有使用更多的 RAM,因为列表只保存对对象的引用,即它不会复制整个对象。【参考方案8】:

我已经使用链接解决了同样的问题,并在其中进行了一些修改。 Search filter on RecyclerView with Cards. Is it even possible?(希望对您有所帮助)。

这是我的适配器类

public class ContactListRecyclerAdapter extends RecyclerView.Adapter<ContactListRecyclerAdapter.ContactViewHolder> implements Filterable 

Context mContext;
ArrayList<Contact> customerList;
ArrayList<Contact> parentCustomerList;


public ContactListRecyclerAdapter(Context context,ArrayList<Contact> customerList)

    this.mContext=context;
    this.customerList=customerList;
    if(customerList!=null)
    parentCustomerList=new ArrayList<>(customerList);


   // other overrided methods

@Override
public Filter getFilter() 
    return new FilterCustomerSearch(this,parentCustomerList);


//过滤器类

import android.widget.Filter;
import java.util.ArrayList;


public class FilterCustomerSearch extends Filter

private final ContactListRecyclerAdapter mAdapter;
ArrayList<Contact> contactList;
ArrayList<Contact> filteredList;

public FilterCustomerSearch(ContactListRecyclerAdapter mAdapter,ArrayList<Contact> contactList) 
    this.mAdapter = mAdapter;
    this.contactList=contactList;
    filteredList=new ArrayList<>();


@Override
protected FilterResults performFiltering(CharSequence constraint) 
    filteredList.clear();
    final FilterResults results = new FilterResults();

    if (constraint.length() == 0) 
        filteredList.addAll(contactList);
     else 
        final String filterPattern = constraint.toString().toLowerCase().trim();

        for (final Contact contact : contactList) 
            if (contact.customerName.contains(constraint)) 
                filteredList.add(contact);
            
            else if (contact.emailId.contains(constraint))
            
                filteredList.add(contact);

            
            else if(contact.phoneNumber.contains(constraint))
                filteredList.add(contact);
        
    
    results.values = filteredList;
    results.count = filteredList.size();
    return results;


@Override
protected void publishResults(CharSequence constraint, FilterResults results) 
    mAdapter.customerList.clear();
    mAdapter.customerList.addAll((ArrayList<Contact>) results.values);
    mAdapter.notifyDataSetChanged();

//活动类

public class HomeCrossFadeActivity extends AppCompatActivity implements View.OnClickListener,OnFragmentInteractionListener,OnTaskCompletedListner

Fragment fragment;
 protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_homecrossfadeslidingpane2);CardView mCard;
   setContentView(R.layout.your_main_xml);
   //other overrided methods
  @Override
   public boolean onCreateOptionsMenu(Menu menu) 
    // Inflate the menu; this adds items to the action bar if it is present.

    MenuInflater inflater = getMenuInflater();
    // Inflate menu to add items to action bar if it is present.
    inflater.inflate(R.menu.menu_customer_view_and_search, menu);
    // Associate searchable configuration with the SearchView
    SearchManager searchManager =
            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView =
            (SearchView) menu.findItem(R.id.menu_search).getActionView();
    searchView.setQueryHint("Search Customer");
    searchView.setSearchableInfo(
            searchManager.getSearchableInfo(getComponentName()));

    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() 
        @Override
        public boolean onQueryTextSubmit(String query) 
            return false;
        

        @Override
        public boolean onQueryTextChange(String newText) 
            if(fragment instanceof CustomerDetailsViewWithModifyAndSearch)
                ((CustomerDetailsViewWithModifyAndSearch)fragment).adapter.getFilter().filter(newText);
            return false;
        
    );



    return true;


在 OnQueryTextChangeListener() 方法中使用您的适配器。我已将其转换为片段,因为我的适配器处于片段中。如果适配器在您的活动类中,您可以直接使用它。

【讨论】:

【参考方案9】:

这是我对扩展@klimat 答案以不丢失过滤动画的看法。

public void filter(String query)
    int completeListIndex = 0;
    int filteredListIndex = 0;
    while (completeListIndex < completeList.size())
        Movie item = completeList.get(completeListIndex);
        if(item.getName().toLowerCase().contains(query))
            if(filteredListIndex < filteredList.size()) 
                Movie filter = filteredList.get(filteredListIndex);
                if (!item.getName().equals(filter.getName())) 
                    filteredList.add(filteredListIndex, item);
                    notifyItemInserted(filteredListIndex);
                
            else
                filteredList.add(filteredListIndex, item);
                notifyItemInserted(filteredListIndex);
            
            filteredListIndex++;
        
        else if(filteredListIndex < filteredList.size())
            Movie filter = filteredList.get(filteredListIndex);
            if (item.getName().equals(filter.getName())) 
                filteredList.remove(filteredListIndex);
                notifyItemRemoved(filteredListIndex);
            
        
        completeListIndex++;
    

基本上它的作用是查看一个完整的列表并将项目逐个添加/删除到过滤后的列表中。

【讨论】:

【参考方案10】:

在您的适配器中添加一个接口。

public interface SelectedUser

    void selectedUser(UserModel userModel);


在您的 mainactivity 中实现接口并覆盖该方法。 @Override public void selectedUser(UserModel userModel)

    startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));




完整教程和源代码: Recyclerview with searchview and onclicklistener

【讨论】:

【参考方案11】:

我建议修改@Xaver Kapeller 的解决方案,在您清除搜索文本(过滤器不再起作用)后,使用以下两点来避免出现问题,因为适配器的列表背面的大小小于过滤器列表,并且发生了 IndexOutOfBoundsException。所以代码需要修改如下

public void addItem(int position, ExampleModel model) 
    if(position >= mModel.size()) 
        mModel.add(model);
        notifyItemInserted(mModel.size()-1);
     else 
        mModels.add(position, model);
        notifyItemInserted(position);
    

并在 moveItem 功能中进行修改

public void moveItem(int fromPosition, int toPosition) 
    final ExampleModel model = mModels.remove(fromPosition);
    if(toPosition >= mModels.size()) 
        mModels.add(model);
        notifyItemMoved(fromPosition, mModels.size()-1);
     else 
        mModels.add(toPosition, model);
        notifyItemMoved(fromPosition, toPosition); 
    

希望对你有帮助!

【讨论】:

完全没有必要。 对于原始答案,如果您不这样做,就会发生 IndexOutOfBoundsException,那么为什么不需要????你想要一个日志吗? @XaverKapeller 不,只有当您以错误的方式实现 Adapter 时才会发生异常。在没有看到您的代码的情况下,我猜最可能的问题是您没有将包含所有项目的列表副本传递给Adapter 错误日志:W/System.err: java.lang.IndexOutOfBoundsException: Invalid index 36, size is 35 W/System.err: at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java: 255) W/System.err: 在 java.util.ArrayList.add(ArrayList.java:147) W/System.err: 在 com.quomodo.inploi.ui.adapter.MultipleSelectFilterAdapter.addItem(MultipleSelectFilterAdapter.java:125) W/System.err:在 com.quomodo.inploi.ui.adapter.MultipleSelectFilterAdapter.applyAndAnimateAdditions(MultipleSelectFilterAdapter.java:78) 请帮忙检查下面的源代码@XaverKapeller gist.github.com/toidv/fe71dc45169e4138271b52fdb29420c5【参考方案12】:

如果你想搜索按钮点击,那么这很好。

filterIcon.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        String strCHR = homeSearchEdit.getText().toString();
        if (homeSearchEdit.getText().toString().length() > 0) 
            ArrayList<ServiceModel> listNew = new ArrayList<>();
            for (int l = 0; l < arrayList.size(); l++) 
                String serviceName = arrayList.get(l).getServiceName().toLowerCase();
                if (serviceName.contains(strCHR.toLowerCase())) 
                    listNew.add(arrayList.get(l));
                
            
            recyclerView.setVisibility(View.VISIBLE);
            adapter = new ServiceAdapter(HomeActivity.this, listNew);
            recyclerView.setAdapter(adapter);
         else 
            recyclerView.setVisibility(View.VISIBLE);
            adapter = new ServiceAdapter(HomeActivity.this, arrayList);
            recyclerView.setAdapter(adapter);
        
    
);

where ,filterIcon 是按钮,homeSearchEdit 是editText(我们申请搜索的地方)。

【讨论】:

以上是关于如何使用 SearchView 过滤 RecyclerView的主要内容,如果未能解决你的问题,请参考以下文章

在使用存储在 JSON 上的 ID 进行 SearchView 过滤后,如何从 ListView 打开 Activity

SearchView 过滤和设置建议

如何更改 SearchView 默认图标?

使用 Room 和 RecyclerView 在 SearchView 中添加过滤器

已解决:SearchView 不会在 TabLayout 的每个子选项卡中进行过滤

去除searchView的黑框去除