如何使用SearchView过滤RecyclerView

时间:2015-05-22 13:39:44

标签: android android-recyclerview searchview android-filterable

我正在尝试从支持库中实现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);
        }
    }
}

12 个答案:

答案 0 :(得分:839)

简介

由于你的问题并不是很清楚你究竟遇到了什么问题,所以我写了一篇关于如何实现这个功能的快速演练。如果你还有问题可以随意提问。

我在这个GitHub Repository中有一个我正在谈论的所有事情的实例 如果您想了解有关示例项目的更多信息,请访问project homepage

在任何情况下,结果都应如下所示:

demo image

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

Get it on Google 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进行充气,然后您可以查找包含MenuItem的{​​{1}}并实施{{ 1}}我们将用它来监听输入SearchView的文本的更改:

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中实施过滤逻辑。

设置onQueryTextChange()

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

Adapter

它只是您的基本模型,它会在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_width="match_parent" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" android:clickable="true"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:text="@{model.text}"/> </FrameLayout> </layout>

ViewHolder

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

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

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

排序列表

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); } } 是一个非常棒的工具,它是Adapter库的一部分。它负责通知SortedList有关数据集的更改,并且这是一种非常有效的方式。它要求你做的唯一事情是指定元素的顺序。您需要通过实施RecyclerView方法来实现这一点,该方法将Adapter中的两个元素与compare()进行比较。但是,它不是对SortedList进行排序,而是用于对Comparator中的项目进行排序!

List通过您必须实施的RecyclerView课程与SortedList互动:

Adapter

在回调顶部的方法中,如Callbackprivate 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(); } } 等,您必须调用onMoved的等效通知方法。底部onInsertedAdaptercompare底部的三种方法必须根据您要显示的对象类型以及这些对象应在屏幕上显示的顺序来实现。< / p>

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

areContentsTheSame

这是我之前谈到的areItemsTheSame方法。在这个例子中,我只是将调用传递给@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); } 应该交叉淡化旧版本和新版本。如果您的模型类具有正确的SortedListRecyclerView实现,您通常可以像上面一样实现它。如果我们向equals()类添加hashCode()equals()实现,它应该如下所示:

hashCode()

快速注意:大多数IDE,如Android Studio,IntelliJ和Eclipse,只需按一下按钮,就可以为您生成ExampleModelpublic 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中的工作方式!

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

equals()

hashCode()使用此方法检查两个项是否引用相同的内容。用最简单的术语(不解释@Override public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) { return item1.getId() == item2.getId(); } 如何工作)这用于确定对象是否已经包含在SortedList中,以及是否需要播放添加,移动或更改动画。如果你的模型有id,你通常会比较这个方法中的id。如果他们不需要找出其他方法来检查这一点,但是最终实现这一点取决于您的特定应用程序。通常,最简单的选择是为所有模型提供一个id - 例如,如果要查询数据库中的数据,则可以是主键字段。

正确实施SortedList后,我们可以创建List的实例:

SortedList.Callback

作为SortedList构造函数中的第一个参数,您需要传递模型的类。另一个参数只是我们在上面定义的final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);

现在让我们开始工作:如果我们使用SortedList实施SortedList.Callback,它应该看起来像这样:

Adapter

用于对项目进行排序的SortedList通过构造函数传递,因此即使项目应以不同的顺序显示,我们也可以使用相同的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模型的remove方法。由于SortedList.Callback只有一个可以删除单个对象的remove方法,我们需要遍历列表并逐个删除模型。在开头调用List会将我们要对SortedList进行的所有更改批处理,并提高性能。当我们致电beginBatchedUpdates()时,会立即通知SortedList所有更改。

此外,您必须了解的是,如果您将一个对象添加到endBatchedUpdates()并且它已经在RecyclerView中,那么它将无法再次添加。相反,SortedList使用SortedList方法判断对象是否已更改 - 以及是否有SortedList中的项目将更新。

无论如何,我通常喜欢的是一种方法,它允许我立即替换areContentsTheSame()中的所有项目。删除RecyclerView以外的所有内容,并添加RecyclerView中缺少的所有项目:

List

此方法再次将所有更新批处理以提高性能。第一个循环是相反的,因为在开始时删除一个项会弄乱它之后出现的所有项的索引,这在某些情况下会导致数据不一致等问题。之后,我们只需使用SortedListpublic 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(); } 添加到List即可添加SortedList中尚未包含的所有项目 - 就像我上面所述 - 更新所有项目已经在addAll()但已更改的内容。

由此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个实例:

List

这里没什么特别的,我们只是实例化ExampleModel并将其设置为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个模型。然后我们将所有模型添加到List

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

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()以及查询字符串。然后,我们在List上致电ExampleModel,并传递replaceAll()返回的已过滤Adapter。我们还必须在List上致电filter(),以确保用户在搜索内容时始终可以看到所有内容。否则,scrollToPosition(0)可能会在过滤时保持向下滚动,然后隐藏一些项目。滚动到顶部可确保在搜索时获得更好的用户体验。

现在唯一要做的就是实现RecyclerView本身:

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()

那就是它!上面的代码将在API级别7及更高版本上运行,从API级别11开始,您可以免费获得项目动画!

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

概括问题并简化适配器

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

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

这使我们可以通过仅实现模型和相应的bind()实现来专注于应该在Adapter中显示的内容。使用此基类,我们不必担心RecyclerView及其ViewHolder的复杂细节。

SortedListAdapter

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

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

SortedListAdapter

您可以找到有关此库on the library homepage的更多信息。

使用SortedListAdapter

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

  • 更改compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1' ,使其展开SortedListAdapter。 type参数应该是应绑定到此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 { ... } 并删除我们不再需要的所有内容。 type参数应该是您正在使用的模型类型 - 在本例中为ExampleAdapter。但是,如果您使用的是不同类型的模型,请将类型参数设置为SortedListAdapter

ExampleModel

之后我们完成了!但最后要提到的一点是: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); } } 所拥有的SortedListAdapteradd()remove()方法。它使用单独的replaceAll()对象来修改列表中可以通过ExampleAdapter方法访问的项目。因此,如果您要删除或添加项目,则需要调用Editor,然后在此edit()个实例上添加和删除项目,完成后,请在其上调用edit()以应用更改到Editor

commit()

您通过这种方式进行的所有更改都会按批处理以提高性能。我们在上面章节中实现的SortedList方法也出现在此mAdapter.edit() .remove(modelToRemove) .add(listOfModelsToAdd) .commit(); 对象上:

replaceAll()

如果您忘记拨打Editor,则不会应用任何更改!

答案 1 :(得分:164)

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

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))中初始化。

如果您这样做,只需从filter致电OnQueryTextListener

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;
    }
});

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

答案 2 :(得分:68)

以更清洁的方式关注@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更改为您想要的类型(例如<CustomerModel>

然后从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;
    }
});

答案 3 :(得分:5)

  

只需在适配器中创建两个列表orignal和一个temp,实现Fildingrable

    @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;
    }

答案 4 :(得分:1)

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

1。。将数据设置为以 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 < / em>

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 ViewModel DAO 实现,是唯一真正的“魔术”接下来是 OnQueryTextListener ,它将随着查询文本的更改动态更新列表的结果。

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

答案 5 :(得分:1)

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

为什么不只是隐藏未找到的元素,而只是将它们的索引存储在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());
            }
        });

结果:

Search example gif

答案 6 :(得分:0)

我建议修改@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); 
    }
}

希望它可以帮到你!

答案 7 :(得分:0)

这是我对扩展@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++;
    }
}

基本上,它的工作是浏览完整的列表,然后逐项添加/删除项目到过滤后的列表中。

答案 8 :(得分:0)

Recyclerview with searchview and clicklistener

在适配器中添加接口。

public interface SelectedUser{

    void selectedUser(UserModel userModel);

}

在您的mainactivity中实现该接口并覆盖该方法。     @Override     公共无效selectedUser(UserModel userModel){

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



}

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

答案 9 :(得分:0)

如果您想在按钮点击时进行搜索,那么这可以正常工作。

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);
        }
    }
});

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

答案 10 :(得分:-1)

我使用链接进行了一些修改,解决了同样的问题。 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()方法中使用适配器。因为我的adpter处于片段状态,所以我将它转换为片段。如果适配器位于您的活动类中,则可以直接使用该适配器。

答案 11 :(得分:-1)

在适配器中:

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;
            }
        });