如何使用SearchView过滤RecyclerView
我正试图从支持库实现SearchView
。 我希望用户能够使用SearchView
来筛选RecyclerView
的电影List
。
到目前为止,我已经遵循了几个教程,并且已经将SearchView
添加到ActionBar
,但是我不确定从哪里开始。 我已经看到了一些例子,但是当你开始input时,没有一个显示结果。
这是我的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); } } }
介绍
既然你不清楚自己有什么问题,就写下这个关于如何实现这个function的快速演练,如果你还有问题可以随意问。
我在这个GitHub仓库中有一个我正在讨论的东西的工作示例。
如果您想了解更多关于示例项目的信息,请访问项目主页 。
无论如何,结果应该如下所示:
如果您首先想要使用演示应用程序,则可以从Play商店安装它:
无论如何,让我们开始吧。
设置SearchView
在文件夹res/menu
创build一个名为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>
在你的Fragment
或Activity
你必须象往常一样使这个菜单xml膨胀,然后你可以查找包含SearchView
的MenuItem
,并实现OnQueryTextListener
,我们将使用它来侦听input到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) MenuItemCompat.getActionView(searchItem); 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()
实现filter逻辑。
设置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>
正如你所看到的,我使用数据绑定。 如果你从未使用过数据绑定,请不要气馁! 这是非常简单和强大的,但我不能解释它是如何工作的这个答案的范围。
这是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
类。
sorting列表
SortedList
是一个完全了不起的工具,它是RecyclerView
库的一部分。 它负责通知Adapter
有关数据集的更改,这样做是非常有效的方法。 它需要你做的唯一的事情是指定元素的顺序。 您需要通过实现compare()
方法来比较SortedList
两个元素,就像Comparator
。 但不是sortingList
,而是用来sortingRecyclerView
的项目!
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(); } }
在callback顶部的方法,如onMoved
, onInserted
等,你必须调用Adapter
的等效通知方法。 这三个方法在底部compare
, areContentsTheSame
和areItemsTheSame
你必须根据你想要显示的对象types以及这些对象应该以什么样的顺序出现在屏幕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); }
这种方法的目的是确定模型的内容是否已经改变。 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都能够在按下button时为您生成equals()
和hashCode()
实现。 所以你不必自己实现它们。 在互联网上查找它在IDE中的工作原理!
现在我们来看看最后一个方法:
@Override public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) { return item1.getId() == item2.getId(); }
SortedList
使用这个方法来检查两个项目是否引用相同的东西。 用最简单的术语(没有解释SortedList
是如何工作的),这用来确定一个对象是否已经包含在List
以及是否需要播放添加,移动或改变animation。 如果你的模型有一个id,你通常会比较这个方法中的id。 如果他们没有,你需要找出其他方式来检查这一点,但是最终实现这取决于你的具体应用程序。 通常,为所有模型提供一个id是最简单的select – 例如,如果您正在查询数据库中的数据,则可能是主键字段。
通过正确实现SortedList.Callback
,我们可以创build一个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(); } }
用于对项目进行sorting的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
执行了这个操作。 除此之外,这些方法的实现非常简单,只有一个例外:删除模型List
的remove方法。 由于SortedList
只有一个remove方法可以删除单个对象,所以我们需要遍历整个列表并逐个删除模型。 在开始时调用beginBatchedUpdates()
会将我们要对SortedList
进行的所有更改批量化,并提高性能。 当我们调用endBatchedUpdates()
, RecyclerView
会立即通知所有更改。
此外,你必须明白的是,如果你添加一个对象到SortedList
,它已经在SortedList
,它将不会再被添加。 相反, SortedList
使用areContentsTheSame()
方法来确定对象是否已经更改 – 如果它有在RecyclerView
的项目将被更新。
无论如何,我通常喜欢的是一种方法,允许我一次replaceRecyclerView
中的所有项目。 删除不在List
中的所有内容,并添加SortedList
中缺less的所有项目:
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(); } }
现在唯一缺less的是实现过滤!
实现filter逻辑
为了实现filter逻辑,我们首先必须定义所有可能模型的List
。 对于这个例子,我从一个电影数组中创build一个ExampleModel
实例List
:
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
数组中的电影名称创build一个模型List
。 然后我们将所有模型添加到SortedList
。
现在我们可以回到之前定义的onQueryTextChange()
,并开始实现filter逻辑:
@Override public boolean onQueryTextChange(String query) { final List<ExampleModel> filteredModelList = filter(mModels, query); mAdapter.replaceAll(filteredModelList); mBinding.recyclerView.scrollToPosition(0); return true; }
这再次非常简单。 我们调用方法filter()
并传入ExampleModel
的List
以及查询string。 然后,我们调用Adapter
上的replaceAll()
并传入filter()
返回的过滤List
。 我们还必须调用RecyclerView
上的scrollToPosition(0)
,以确保用户在search时总能看到所有项目。 否则, RecyclerView
可能会停留在向下滚动的位置,同时过滤并隐藏几个项目。 滚动到顶部可确保在search时获得更好的用户体验。
现在唯一要做的就是实现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; }
我们在这里做的第一件事是在查询string上调用toLowerCase()
。 我们不希望我们的searchfunction是区分大小写的,通过在所有比较的string上调用toLowerCase()
,我们可以确保我们返回相同的结果,而不pipe大小写。 然后它只是遍历我们传入的List
中的所有模型,并检查查询string是否包含在模型的文本中。 如果是,则将模型添加到过滤的List
。
而就是这样! 上面的代码将运行在API级别7及以上,从API级别11开始,您可以免费获得项目animation!
我意识到这是一个非常详细的描述,可能会使整个事情看起来比实际更加复杂,但是我们可以推广这个问题,并且使得基于SortedList
的Adapter
实现起来更加简单。
推广问题并简化适配器
在本节中,我不会详细讨论 – 部分原因是由于我在堆栈溢出问题上遇到了字符数限制问题,但也因为大部分已经在上面解释过了 – 但总结了一些变化:我们可以实现一个基础Adapter
已经负责处理SortedList
以及将模型绑定到ViewHolder
实例的类,并提供了一种基于SortedList
实现Adapter
的简便方法。 为此,我们必须做两件事:
- 我们需要创build一个所有模型类必须实现的
ViewModel
接口 - 我们需要创build一个
ViewHolder
子类,该子类定义了Adapter
可以用来自动绑定模型的bind()
方法。
这允许我们只关注应该在RecyclerView
显示的内容,只需实现模型和相应的ViewHolder
实现。 使用这个基类,我们不必担心Adapter
和SortedList
的复杂细节。
SortedListAdapter
由于StackOverflow的字符数限制,我不能通过实现这个基类的每个步骤,甚至在这里添加完整的源代码,但是你可以find这个基类的完整源代码 – 我把它称为SortedListAdapter
这个GitHub Gist 。
为了简单起见,我在jCenter上发布了一个包含SortedListAdapter
的库。 如果你想使用它,那么你所需要做的就是将这个依赖添加到你的应用程序的build.gradle文件中:
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
你可以在图书馆的主页上find关于这个图书馆的更多信息。
使用SortedListAdapter
要使用SortedListAdapter
我们必须做两个更改:
-
更改
ViewHolder
,使其扩展SortedListAdapter.ViewHolder
。 types参数应该是应该绑定到这个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
并删除我们不需要的所有东西。 types参数应该是您正在使用的模型的types – 在这种情况下是ExampleModel
。 但是,如果您正在使用不同types的模型,请将types参数设置为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()
则不会应用您的更改!
所有你需要做的就是在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; } });
这是通过姓名和电话号码过滤我的电话簿的一个例子。
在@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; GenericRecycleAdapter.this.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是一个genericstypes,你可以使用你的类来扩展它:
public class customerAdapter extends GenericRecycleAdapter<CustomerModel>
或者把E改成你想要的types(例如<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; } });
只需在适配器中创build两个列表orignal和一个temp并实现Filterable 。
@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; }
我build议修改@Xaver Kapeller的解决scheme,以避免在清除search后的文本(filter不再工作)之后出现问题,因为适配器的列表背面的大小小于filter列表的大小,并且发生了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); } }
And modify also in moveItem functionality
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); } }
Hope that It could help you!
I have solved the same problem using the link with some modifications in it. Search filter on RecyclerView with Cards. 这甚至有可能吗? (hope this helps).
Here is my adapter class
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); } }
//Filter class
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(); }
}
//Activity class
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; } }
In OnQueryTextChangeListener() method use your adapter. I have casted it to fragment as my adpter is in fragment. You can use the adapter directly if its in your activity class.
In Adapter:
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; } });
- 更改TextView中一个单词的文本颜色
- android getIntrinsicHeight和getIntrinsicWidth是什么意思?
- Android Studio更新项目:合并与重build与分支默认
- 线程“main”中的exceptionjava.lang.NoClassDefFoundError:junit / textui / ResultPrinter
- android:在触摸移动时移动视图(ACTION_MOVE)
- android:我如何从我的应用程序打开另一个应用程序?
- Android NDK构build,Method无法parsing
- 如何从AsyncTask返回一个string?
- 片段共享元素转换不适用于ViewPager