如何使用 SearchView 过滤 RecyclerView
How to filter a RecyclerView with a SearchView
我正在尝试从支持库中实现 SearchView
。我希望用户使用 SearchView
过滤 RecyclerView
中的 List
部电影。
到目前为止,我已经学习了一些教程,并将 SearchView
添加到 ActionBar
,但我不太确定从这里到哪里去。我看过一些示例,但其中 none 会在您开始输入时显示结果。
这是我的 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);
}
}
}
简介
由于您的问题并不清楚您到底遇到了什么问题,因此我写了这篇关于如何实现此功能的快速演练;如果您还有问题,请随时提问。
我在这个 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>
在您的 Fragment
或 Activity
中,您必须像往常一样扩充此菜单 xml,然后您可以查找包含 [=35= 的 MenuItem
] 并实现 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
首先这是模型 class 我将用于此示例:
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
class:
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);
}
}
同样没什么特别的。它只是使用数据绑定将模型 class 绑定到我们在上面的布局 xml 中定义的布局。
现在我们终于可以进入真正有趣的部分了:编写适配器。我将跳过 Adapter
的基本实现,而是专注于与此答案相关的部分。
但首先我们要谈一件事: SortedList
class.
排序列表
SortedList
是一个非常了不起的工具,它是 RecyclerView
库的一部分。它负责通知 Adapter
有关数据集的更改,这是一种非常有效的方式。它要求您做的唯一一件事就是指定元素的顺序。您需要通过实现一个 compare()
方法来做到这一点,该方法比较 SortedList
中的两个元素,就像 Comparator
一样。但是,它不是对 List
进行排序,而是用于对 RecyclerView
!
中的项目进行排序
SortedList
通过 Callback
class 与 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();
}
}
在回调顶部的方法中,如 onMoved
、onInserted
等,您必须调用 Adapter
的等效通知方法。最下面的compare
、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);
}
此方法的目的是确定模型的内容是否已更改。 SortedList
使用它来确定是否需要调用更改事件 - 换句话说,RecyclerView
是否应该交叉淡入淡出旧版本和新版本。如果你的模型 classes 有一个正确的 equals()
和 hashCode()
实现,你通常可以像上面那样实现它。如果我们将 equals()
和 hashCode()
实现添加到 ExampleModel
class 它应该看起来像这样:
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()
实现的功能一个按钮!所以你不必自己实施它们。在互联网上查找它在 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
构造函数中的第一个参数,您需要传递模型的 class。另一个参数就是我们上面定义的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
完成了!除此之外,这些方法的实现非常简单,只有一个例外:删除 List
个模型的 remove 方法。由于 SortedList
只有一个可以删除单个对象的删除方法,我们需要遍历列表并一个接一个地删除模型。在开始时调用 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
所有可能的模型。对于这个例子,我从电影数组中创建了 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
。之后,我们根据 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()
并传入 ExampleModel
的 List
以及查询字符串。然后我们在 Adapter
上调用 replaceAll()
并传入由 filter()
过滤的 List
return。我们还必须在 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()
,我们可以确保我们 return 无论大小写都得到相同的结果。然后它只是遍历我们传递给它的 List
中的所有模型,并检查查询字符串是否包含在模型的文本中。如果是,则将模型添加到过滤后的 List
.
就是这样!上面的代码将 运行 API 级别 7 及以上,从 API 级别 11 开始,您将免费获得项目动画!
我意识到这是一个非常详细的描述,可能使整个事情看起来比实际情况更复杂,但是有一种方法可以概括整个问题并实现 Adapter
基于SortedList
简单多了。
概括问题并简化适配器
在本节中,我不会详细介绍 - 部分原因是我 运行 反对 Stack Overflow 上答案的字符限制,但也因为其中大部分内容已经在上面解释过 - 但总结变化:我们可以实现一个基础 Adapter
class,它已经负责处理 SortedList
以及将模型绑定到 ViewHolder
实例,并提供了一种方便的方法来基于 SortedList
实施 Adapter
。为此,我们必须做两件事:
- 我们需要创建一个
ViewModel
接口,所有模型 classes 都必须实现该接口
- 我们需要创建一个
ViewHolder
subclass,它定义了一个 bind()
方法,Adapter
可以使用它来自动绑定模型。
这使我们能够通过实施模型和相应的 ViewHolder
实施,只关注应该在 RecyclerView
中显示的内容。使用这个基础 class 我们不必担心 Adapter
及其 SortedList
.
的复杂细节
SortedListAdapter
由于 Whosebug 上答案的字符数限制,我无法完成实现此基础的每个步骤 class,甚至无法在此处添加完整的源代码,但您可以找到完整的源代码基础 class - 我称它为 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
并删除我们不再需要的所有内容。类型参数应该是您正在使用的模型类型 - 在本例中为 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);
}
}
之后我们就完成了!然而,最后一件事要提到: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()
,那么 none 的更改将被应用!
我使用 link 解决了同样的问题,只是做了一些修改。 (希望对您有所帮助)。
这是我的适配器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);
}
}
//过滤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;
}
}
在 OnQueryTextChangeListener() 方法中使用您的适配器。我已将其转换为片段,因为我的适配器处于片段状态。如果它在您的 activity class.
中,您可以直接使用适配器
你需要做的就是在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;
}
});
这是按名称和 phone 编号过滤我的 phone 图书的示例。
以更简洁的方式跟随@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是一个通用类型,你可以使用你的class:
来扩展它
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;
}
});
我建议用以下两点修改@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);
}
}
希望对您有所帮助!
simply create two list in adapter one orignal and one temp and implements 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;
}
在适配器中:
public void setFilter(List<Channel> newList){
mChannels = new ArrayList<>();
mChannels.addAll(newList);
notifyDataSetChanged();
}
在Activity中:
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;
}
});
使用 Android 架构组件 通过使用 LiveData 这可以很容易地用任何类型的 适配器。您只需执行以下步骤:
1. 将您的数据从 房间 数据库设置为 return作为 LiveData 如下例所示:
@Dao
public interface CustomDAO{
@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
public LiveData<List<Word>> searchFor(String searchquery);
}
2. 创建一个 ViewModel 对象以通过连接您的 DAO[ 的方法实时更新您的数据=48=] 和你的 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 class 中的某处设置您的查询侦听器,如下所示
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 实现,这里唯一真正的 "magic" 发生在 OnQueryTextListener 中,它将随着查询文本的变化动态更新列表的结果。
如果您需要更多关于此事的说明,请随时询问。
我希望这对您有所帮助 :).
这是我对扩展@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++;
}
}
基本上它所做的是通过完整列表和 adding/removing 项逐一查看过滤列表。
在您的适配器中添加接口。
public interface SelectedUser{
void selectedUser(UserModel userModel);
}
在您的主活动中实现接口并覆盖该方法。
@覆盖
public void selectedUser(UserModel userModel) {
startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));
}
我不知道为什么每个人都使用同一个列表的2份来解决这个问题。这使用了太多内存...
为什么不直接隐藏找不到的元素,而简单地将它们的索引存储在Set
中以后可以恢复吗?内存要少得多,尤其是当您的对象很大时。
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());
}
});
结果:
如果你想在按钮点击时搜索,那么这很好。
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);
}
}
});
其中 ,filterIcon 是按钮,homeSearchEdit是editText(我们申请搜索的地方)。
我正在尝试从支持库中实现 SearchView
。我希望用户使用 SearchView
过滤 RecyclerView
中的 List
部电影。
到目前为止,我已经学习了一些教程,并将 SearchView
添加到 ActionBar
,但我不太确定从这里到哪里去。我看过一些示例,但其中 none 会在您开始输入时显示结果。
这是我的 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);
}
}
}
简介
由于您的问题并不清楚您到底遇到了什么问题,因此我写了这篇关于如何实现此功能的快速演练;如果您还有问题,请随时提问。
我在这个 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>
在您的 Fragment
或 Activity
中,您必须像往常一样扩充此菜单 xml,然后您可以查找包含 [=35= 的 MenuItem
] 并实现 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
首先这是模型 class 我将用于此示例:
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
class:
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);
}
}
同样没什么特别的。它只是使用数据绑定将模型 class 绑定到我们在上面的布局 xml 中定义的布局。
现在我们终于可以进入真正有趣的部分了:编写适配器。我将跳过 Adapter
的基本实现,而是专注于与此答案相关的部分。
但首先我们要谈一件事: SortedList
class.
排序列表
SortedList
是一个非常了不起的工具,它是 RecyclerView
库的一部分。它负责通知 Adapter
有关数据集的更改,这是一种非常有效的方式。它要求您做的唯一一件事就是指定元素的顺序。您需要通过实现一个 compare()
方法来做到这一点,该方法比较 SortedList
中的两个元素,就像 Comparator
一样。但是,它不是对 List
进行排序,而是用于对 RecyclerView
!
SortedList
通过 Callback
class 与 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();
}
}
在回调顶部的方法中,如 onMoved
、onInserted
等,您必须调用 Adapter
的等效通知方法。最下面的compare
、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);
}
此方法的目的是确定模型的内容是否已更改。 SortedList
使用它来确定是否需要调用更改事件 - 换句话说,RecyclerView
是否应该交叉淡入淡出旧版本和新版本。如果你的模型 classes 有一个正确的 equals()
和 hashCode()
实现,你通常可以像上面那样实现它。如果我们将 equals()
和 hashCode()
实现添加到 ExampleModel
class 它应该看起来像这样:
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()
实现的功能一个按钮!所以你不必自己实施它们。在互联网上查找它在 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
构造函数中的第一个参数,您需要传递模型的 class。另一个参数就是我们上面定义的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
完成了!除此之外,这些方法的实现非常简单,只有一个例外:删除 List
个模型的 remove 方法。由于 SortedList
只有一个可以删除单个对象的删除方法,我们需要遍历列表并一个接一个地删除模型。在开始时调用 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
所有可能的模型。对于这个例子,我从电影数组中创建了 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
。之后,我们根据 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()
并传入 ExampleModel
的 List
以及查询字符串。然后我们在 Adapter
上调用 replaceAll()
并传入由 filter()
过滤的 List
return。我们还必须在 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()
,我们可以确保我们 return 无论大小写都得到相同的结果。然后它只是遍历我们传递给它的 List
中的所有模型,并检查查询字符串是否包含在模型的文本中。如果是,则将模型添加到过滤后的 List
.
就是这样!上面的代码将 运行 API 级别 7 及以上,从 API 级别 11 开始,您将免费获得项目动画!
我意识到这是一个非常详细的描述,可能使整个事情看起来比实际情况更复杂,但是有一种方法可以概括整个问题并实现 Adapter
基于SortedList
简单多了。
概括问题并简化适配器
在本节中,我不会详细介绍 - 部分原因是我 运行 反对 Stack Overflow 上答案的字符限制,但也因为其中大部分内容已经在上面解释过 - 但总结变化:我们可以实现一个基础 Adapter
class,它已经负责处理 SortedList
以及将模型绑定到 ViewHolder
实例,并提供了一种方便的方法来基于 SortedList
实施 Adapter
。为此,我们必须做两件事:
- 我们需要创建一个
ViewModel
接口,所有模型 classes 都必须实现该接口 - 我们需要创建一个
ViewHolder
subclass,它定义了一个bind()
方法,Adapter
可以使用它来自动绑定模型。
这使我们能够通过实施模型和相应的 ViewHolder
实施,只关注应该在 RecyclerView
中显示的内容。使用这个基础 class 我们不必担心 Adapter
及其 SortedList
.
SortedListAdapter
由于 Whosebug 上答案的字符数限制,我无法完成实现此基础的每个步骤 class,甚至无法在此处添加完整的源代码,但您可以找到完整的源代码基础 class - 我称它为 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
并删除我们不再需要的所有内容。类型参数应该是您正在使用的模型类型 - 在本例中为 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);
}
}
之后我们就完成了!然而,最后一件事要提到: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()
,那么 none 的更改将被应用!
我使用 link 解决了同样的问题,只是做了一些修改。
这是我的适配器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);
}
}
//过滤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;
}
}
在 OnQueryTextChangeListener() 方法中使用您的适配器。我已将其转换为片段,因为我的适配器处于片段状态。如果它在您的 activity class.
中,您可以直接使用适配器你需要做的就是在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;
}
});
这是按名称和 phone 编号过滤我的 phone 图书的示例。
以更简洁的方式跟随@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是一个通用类型,你可以使用你的class:
来扩展它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;
}
});
我建议用以下两点修改@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);
}
}
希望对您有所帮助!
simply create two list in adapter one orignal and one temp and implements 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;
}
在适配器中:
public void setFilter(List<Channel> newList){
mChannels = new ArrayList<>();
mChannels.addAll(newList);
notifyDataSetChanged();
}
在Activity中:
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;
}
});
使用 Android 架构组件 通过使用 LiveData 这可以很容易地用任何类型的 适配器。您只需执行以下步骤:
1. 将您的数据从 房间 数据库设置为 return作为 LiveData 如下例所示:
@Dao
public interface CustomDAO{
@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
public LiveData<List<Word>> searchFor(String searchquery);
}
2. 创建一个 ViewModel 对象以通过连接您的 DAO[ 的方法实时更新您的数据=48=] 和你的 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 class 中的某处设置您的查询侦听器,如下所示
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 实现,这里唯一真正的 "magic" 发生在 OnQueryTextListener 中,它将随着查询文本的变化动态更新列表的结果。
如果您需要更多关于此事的说明,请随时询问。 我希望这对您有所帮助 :).
这是我对扩展@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++;
}
}
基本上它所做的是通过完整列表和 adding/removing 项逐一查看过滤列表。
在您的适配器中添加接口。
public interface SelectedUser{
void selectedUser(UserModel userModel);
}
在您的主活动中实现接口并覆盖该方法。 @覆盖 public void selectedUser(UserModel userModel) {
startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));
}
我不知道为什么每个人都使用同一个列表的2份来解决这个问题。这使用了太多内存...
为什么不直接隐藏找不到的元素,而简单地将它们的索引存储在Set
中以后可以恢复吗?内存要少得多,尤其是当您的对象很大时。
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());
}
});
结果:
如果你想在按钮点击时搜索,那么这很好。
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);
}
}
});
其中 ,filterIcon 是按钮,homeSearchEdit是editText(我们申请搜索的地方)。