我正在尝试从支持库中实现SearchView
。我希望用户使用SearchView
过滤List
中的RecyclerView
部电影。
到目前为止,我已经按照了一些教程,我已将SearchView
添加到ActionBar
,但我不确定从何处开始。我看过几个例子,但是当你开始输入时,它们都没有显示结果。
这是我的MainActivity
:
public class MainActivity extends ActionBarActivity {
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
RecyclerView.Adapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new CardAdapter() {
@Override
public Filter getFilter() {
return null;
}
};
mRecyclerView.setAdapter(mAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
这是我的Adapter
:
public abstract class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> implements Filterable {
List<Movie> mItems;
public CardAdapter() {
super();
mItems = new ArrayList<Movie>();
Movie movie = new Movie();
movie.setName("Spiderman");
movie.setRating("92");
mItems.add(movie);
movie = new Movie();
movie.setName("Doom 3");
movie.setRating("91");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers");
movie.setRating("88");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 2");
movie.setRating("87");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 3");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Noah");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 2");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 3");
movie.setRating("86");
mItems.add(movie);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
Movie movie = mItems.get(i);
viewHolder.tvMovie.setText(movie.getName());
viewHolder.tvMovieRating.setText(movie.getRating());
}
@Override
public int getItemCount() {
return mItems.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
public TextView tvMovie;
public TextView tvMovieRating;
public ViewHolder(View itemView) {
super(itemView);
tvMovie = (TextView)itemView.findViewById(R.id.movieName);
tvMovieRating = (TextView)itemView.findViewById(R.id.movieRating);
}
}
}
答案 0 :(得分:839)
由于你的问题并不是很清楚你究竟遇到了什么问题,所以我写了一篇关于如何实现这个功能的快速演练。如果你还有问题可以随意提问。
我在这个GitHub Repository中有一个我正在谈论的所有事情的实例 如果您想了解有关示例项目的更多信息,请访问project homepage。
在任何情况下,结果都应如下所示:
如果您首先想要使用演示应用程序,可以从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进行充气,然后您可以查找包含MenuItem
的{{1}}并实施{{ 1}}我们将用它来监听输入SearchView
的文本的更改:
OnQueryTextListener
现在可以使用SearchView
了。我们将在完成@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
final MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setOnQueryTextListener(this);
return true;
}
@Override
public boolean onQueryTextChange(String query) {
// Here is where we are going to implement the filter logic
return false;
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
的实施后,在SearchView
中实施过滤逻辑。
onQueryTextChange()
首先,这是我将用于此示例的模型类:
Adapter
它只是您的基本模型,它会在Adapter
中显示文字。这是我将用于显示文本的布局:
public class ExampleModel {
private final long mId;
private final String mText;
public ExampleModel(long id, String text) {
mId = id;
mText = text;
}
public long getId() {
return mId;
}
public String getText() {
return mText;
}
}
如您所见,我使用数据绑定。如果您从未使用数据绑定,请不要气馁!它非常简单和强大,但是我无法解释它在这个答案的范围内是如何工作的。
这是RecyclerView
类的<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="com.github.wrdlbrnft.searchablerecyclerviewdemo.ui.models.ExampleModel"/>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@{model.text}"/>
</FrameLayout>
</layout>
:
ViewHolder
再一点没什么特别的。它只是使用数据绑定将模型类绑定到此布局,就像我们在上面的布局xml中定义的那样。
现在我们终于可以找到真正有趣的部分:编写适配器。我将跳过ExampleModel
的基本实现,而是专注于与此答案相关的部分。
但首先我们要讨论一件事:SortedList
类。
public class ExampleViewHolder extends RecyclerView.ViewHolder {
private final ItemExampleBinding mBinding;
public ExampleViewHolder(ItemExampleBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
public void bind(ExampleModel item) {
mBinding.setModel(item);
}
}
是一个非常棒的工具,它是Adapter
库的一部分。它负责通知SortedList
有关数据集的更改,并且这是一种非常有效的方式。它要求你做的唯一事情是指定元素的顺序。您需要通过实施RecyclerView
方法来实现这一点,该方法将Adapter
中的两个元素与compare()
进行比较。但是,它不是对SortedList
进行排序,而是用于对Comparator
中的项目进行排序!
List
通过您必须实施的RecyclerView
课程与SortedList
互动:
Adapter
在回调顶部的方法中,如Callback
,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
底部的三种方法必须根据您要显示的对象类型以及这些对象应在屏幕上显示的顺序来实现。< / p>
让我们逐一介绍这些方法:
areContentsTheSame
这是我之前谈到的areItemsTheSame
方法。在这个例子中,我只是将调用传递给@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
,它比较了两个模型。如果您希望项目按字母顺序显示在屏幕上。这个比较器可能如下所示:
compare()
现在让我们来看看下一个方法:
Comparator
此方法的目的是确定模型的内容是否已更改。 private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
使用它来确定是否需要调用更改事件 - 换句话说,如果@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
应该交叉淡化旧版本和新版本。如果您的模型类具有正确的SortedList
和RecyclerView
实现,您通常可以像上面一样实现它。如果我们向equals()
类添加hashCode()
和equals()
实现,它应该如下所示:
hashCode()
快速注意:大多数IDE,如Android Studio,IntelliJ和Eclipse,只需按一下按钮,就可以为您生成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中的工作方式!
现在让我们来看看最后一种方法:
equals()
hashCode()
使用此方法检查两个项是否引用相同的内容。用最简单的术语(不解释@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
如何工作)这用于确定对象是否已经包含在SortedList
中,以及是否需要播放添加,移动或更改动画。如果你的模型有id,你通常会比较这个方法中的id。如果他们不需要找出其他方法来检查这一点,但是最终实现这一点取决于您的特定应用程序。通常,最简单的选择是为所有模型提供一个id - 例如,如果要查询数据库中的数据,则可以是主键字段。
正确实施SortedList
后,我们可以创建List
的实例:
SortedList.Callback
作为SortedList
构造函数中的第一个参数,您需要传递模型的类。另一个参数只是我们在上面定义的final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);
。
现在让我们开始工作:如果我们使用SortedList
实施SortedList.Callback
,它应该看起来像这样:
Adapter
用于对项目进行排序的SortedList
通过构造函数传递,因此即使项目应以不同的顺序显示,我们也可以使用相同的public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {
private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
});
private final LayoutInflater mInflater;
private final Comparator<ExampleModel> mComparator;
public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
mInflater = LayoutInflater.from(context);
mComparator = comparator;
}
@Override
public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
return new ExampleViewHolder(binding);
}
@Override
public void onBindViewHolder(ExampleViewHolder holder, int position) {
final ExampleModel model = mSortedList.get(position);
holder.bind(model);
}
@Override
public int getItemCount() {
return mSortedList.size();
}
}
。
现在我们差不多完成了!但我们首先需要一种方法来向Comparator
添加或删除项目。为此,我们可以向Adapter
添加方法,以便我们向Adapter
添加和删除项目:
Adapter
我们不需要在此处调用任何通知方法,因为SortedList
已通过public void add(ExampleModel model) {
mSortedList.add(model);
}
public void remove(ExampleModel model) {
mSortedList.remove(model);
}
public void add(List<ExampleModel> models) {
mSortedList.addAll(models);
}
public void remove(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (ExampleModel model : models) {
mSortedList.remove(model);
}
mSortedList.endBatchedUpdates();
}
执行此操作!除此之外,这些方法的实现非常简单,只有一个例外:删除SortedList
模型的remove方法。由于SortedList.Callback
只有一个可以删除单个对象的remove方法,我们需要遍历列表并逐个删除模型。在开头调用List
会将我们要对SortedList
进行的所有更改批处理,并提高性能。当我们致电beginBatchedUpdates()
时,会立即通知SortedList
所有更改。
此外,您必须了解的是,如果您将一个对象添加到endBatchedUpdates()
并且它已经在RecyclerView
中,那么它将无法再次添加。相反,SortedList
使用SortedList
方法判断对象是否已更改 - 以及是否有SortedList
中的项目将更新。
无论如何,我通常喜欢的是一种方法,它允许我立即替换areContentsTheSame()
中的所有项目。删除RecyclerView
以外的所有内容,并添加RecyclerView
中缺少的所有项目:
List
此方法再次将所有更新批处理以提高性能。第一个循环是相反的,因为在开始时删除一个项会弄乱它之后出现的所有项的索引,这在某些情况下会导致数据不一致等问题。之后,我们只需使用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();
}
添加到List
即可添加SortedList
中尚未包含的所有项目 - 就像我上面所述 - 更新所有项目已经在addAll()
但已更改的内容。
由此SortedList
已完成。整件事应该是这样的:
SortedList
现在唯一缺少的是实施过滤!
要实现过滤器逻辑,我们首先必须定义所有可能模型的Adapter
。在本例中,我从一组电影中创建了public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {
private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1 == item2;
}
});
private final Comparator<ExampleModel> mComparator;
private final LayoutInflater mInflater;
public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
mInflater = LayoutInflater.from(context);
mComparator = comparator;
}
@Override
public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(mInflater, parent, false);
return new ExampleViewHolder(binding);
}
@Override
public void onBindViewHolder(ExampleViewHolder holder, int position) {
final ExampleModel model = mSortedList.get(position);
holder.bind(model);
}
public void add(ExampleModel model) {
mSortedList.add(model);
}
public void remove(ExampleModel model) {
mSortedList.remove(model);
}
public void add(List<ExampleModel> models) {
mSortedList.addAll(models);
}
public void remove(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (ExampleModel model : models) {
mSortedList.remove(model);
}
mSortedList.endBatchedUpdates();
}
public void replaceAll(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (int i = mSortedList.size() - 1; i >= 0; i--) {
final ExampleModel model = mSortedList.get(i);
if (!models.contains(model)) {
mSortedList.remove(model);
}
}
mSortedList.addAll(models);
mSortedList.endBatchedUpdates();
}
@Override
public int getItemCount() {
return mSortedList.size();
}
}
个List
个实例:
List
这里没什么特别的,我们只是实例化ExampleModel
并将其设置为private static final String[] MOVIES = new String[]{
...
};
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
private ExampleAdapter mAdapter;
private List<ExampleModel> mModels;
private RecyclerView mRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mAdapter = new ExampleAdapter(this, ALPHABETICAL_COMPARATOR);
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
mBinding.recyclerView.setAdapter(mAdapter);
mModels = new ArrayList<>();
for (String movie : MOVIES) {
mModels.add(new ExampleModel(movie));
}
mAdapter.add(mModels);
}
。之后,我们从Adapter
数组中的电影名称创建RecyclerView
个模型。然后我们将所有模型添加到List
。
现在我们可以回到之前定义的MOVIES
并开始实现过滤器逻辑:
SortedList
这再次非常直截了当。我们调用方法onQueryTextChange()
并传入@Override
public boolean onQueryTextChange(String query) {
final List<ExampleModel> filteredModelList = filter(mModels, query);
mAdapter.replaceAll(filteredModelList);
mBinding.recyclerView.scrollToPosition(0);
return true;
}
filter()
以及查询字符串。然后,我们在List
上致电ExampleModel
,并传递replaceAll()
返回的已过滤Adapter
。我们还必须在List
上致电filter()
,以确保用户在搜索内容时始终可以看到所有内容。否则,scrollToPosition(0)
可能会在过滤时保持向下滚动,然后隐藏一些项目。滚动到顶部可确保在搜索时获得更好的用户体验。
现在唯一要做的就是实现RecyclerView
本身:
RecyclerView
我们在这里做的第一件事是在查询字符串上调用filter()
。我们不希望我们的搜索功能区分大小写,并且通过在我们比较的所有字符串上调用private static List<ExampleModel> filter(List<ExampleModel> models, String query) {
final String lowerCaseQuery = query.toLowerCase();
final List<ExampleModel> filteredModelList = new ArrayList<>();
for (ExampleModel model : models) {
final String text = model.getText().toLowerCase();
if (text.contains(lowerCaseQuery)) {
filteredModelList.add(model);
}
}
return filteredModelList;
}
,我们可以确保无论如何都返回相同的结果。然后它只是迭代我们传入它的toLowerCase()
中的所有模型,并检查查询字符串是否包含在模型的文本中。如果是,则将模型添加到已过滤的toLowerCase()
。
那就是它!上面的代码将在API级别7及更高版本上运行,从API级别11开始,您可以免费获得项目动画!
我意识到这是一个非常详细的描述,可能会使整个事情看起来比实际上更复杂,但有一种方法我们可以概括整个问题,并基于{List
来实现List
{1}}更简单。
在本节中,我不打算详细介绍 - 部分原因是因为我遇到Stack Overflow上的答案的字符限制,但也因为大部分内容已在上面解释过 - 但总结了这些变化:我们可以实现一个基础Adapter
类,它已经负责处理SortedList
以及绑定模型到Adapter
个实例,并提供了一种基于{SortedList
实现ViewHolder
的便捷方法{1}}。为此,我们必须做两件事:
Adapter
接口,所有模型类都必须实现SortedList
子类,它定义ViewModel
方法,ViewHolder
可以用来自动绑定模型。这使我们可以通过仅实现模型和相应的bind()
实现来专注于应该在Adapter
中显示的内容。使用此基类,我们不必担心RecyclerView
及其ViewHolder
的复杂细节。
由于StackOverflow上答案的字符限制,我无法完成实现此基类的每个步骤,甚至可以在此处添加完整的源代码,但是您可以找到此基类的完整源代码 - I称之为Adapter
- 在此GitHub Gist。
为了简化您的生活,我在jCenter上发布了一个包含SortedList
的库!如果您想使用它,那么您需要做的就是将此依赖项添加到应用程序的build.gradle文件中:
SortedListAdapter
您可以找到有关此库on the library homepage的更多信息。
要使用SortedListAdapter
,我们必须进行两项更改:
更改compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
,使其展开SortedListAdapter
。 type参数应该是应绑定到此ViewHolder
的模型 - 在本例中为SortedListAdapter.ViewHolder
。您必须在ViewHolder
而不是ExampleModel
中将数据绑定到模型。
performBind()
确保所有模型都实现bind()
界面:
public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> {
private final ItemExampleBinding mBinding;
public ExampleViewHolder(ItemExampleBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
@Override
protected void performBind(ExampleModel item) {
mBinding.setModel(item);
}
}
之后,我们只需更新ViewModel
以扩展public class ExampleModel implements SortedListAdapter.ViewModel {
...
}
并删除我们不再需要的所有内容。 type参数应该是您正在使用的模型类型 - 在本例中为ExampleAdapter
。但是,如果您使用的是不同类型的模型,请将类型参数设置为SortedListAdapter
。
ExampleModel
之后我们完成了!但最后要提到的一点是:ViewModel
没有我们原始public class ExampleAdapter extends SortedListAdapter<ExampleModel> {
public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
super(context, ExampleModel.class, comparator);
}
@Override
protected ViewHolder<? extends ExampleModel> onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
return new ExampleViewHolder(binding);
}
@Override
protected boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
@Override
protected boolean areItemContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
}
所拥有的SortedListAdapter
,add()
或remove()
方法。它使用单独的replaceAll()
对象来修改列表中可以通过ExampleAdapter
方法访问的项目。因此,如果您要删除或添加项目,则需要调用Editor
,然后在此edit()
个实例上添加和删除项目,完成后,请在其上调用edit()
以应用更改到Editor
:
commit()
您通过这种方式进行的所有更改都会按批处理以提高性能。我们在上面章节中实现的SortedList
方法也出现在此mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();
对象上:
replaceAll()
如果您忘记拨打Editor
,则不会应用任何更改!
答案 1 :(得分:164)
您需要做的就是在filter
中添加RecyclerView.Adapter
方法:
public void filter(String text) {
items.clear();
if(text.isEmpty()){
items.addAll(itemsCopy);
} else{
text = text.toLowerCase();
for(PhoneBookItem item: itemsCopy){
if(item.name.toLowerCase().contains(text) || item.phone.toLowerCase().contains(text)){
items.add(item);
}
}
}
notifyDataSetChanged();
}
itemsCopy
在适配器的构造函数(如itemsCopy.addAll(items)
)中初始化。
如果您这样做,只需从filter
致电OnQueryTextListener
:
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
adapter.filter(query);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
adapter.filter(newText);
return true;
}
});
这是通过姓名和电话号码过滤我的电话簿的一个例子。
答案 2 :(得分:68)
以更清洁的方式关注@Shruthi Kamoji,我们可以使用可过滤的,它的意思是:
public abstract class GenericRecycleAdapter<E> extends RecyclerView.Adapter implements Filterable
{
protected List<E> list;
protected List<E> originalList;
protected Context context;
public GenericRecycleAdapter(Context context,
List<E> list)
{
this.originalList = list;
this.list = list;
this.context = context;
}
...
@Override
public Filter getFilter() {
return new Filter() {
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list = (List<E>) results.values;
notifyDataSetChanged();
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<E> filteredResults = null;
if (constraint.length() == 0) {
filteredResults = originalList;
} else {
filteredResults = getFilteredResults(constraint.toString().toLowerCase());
}
FilterResults results = new FilterResults();
results.values = filteredResults;
return results;
}
};
}
protected List<E> getFilteredResults(String constraint) {
List<E> results = new ArrayList<>();
for (E item : originalList) {
if (item.getName().toLowerCase().contains(constraint)) {
results.add(item);
}
}
return results;
}
}
这里的E是通用类型,您可以使用您的类扩展它:
public class customerAdapter extends GenericRecycleAdapter<CustomerModel>
或者只是将E更改为您想要的类型(例如<CustomerModel>
)
然后从searchView(你可以放在menu.xml上的小部件):
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String text) {
return false;
}
@Override
public boolean onQueryTextChange(String text) {
yourAdapter.getFilter().filter(text);
return true;
}
});
答案 3 :(得分:5)
只需在适配器中创建两个列表orignal和一个temp,实现Fildingrable 。
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
final FilterResults oReturn = new FilterResults();
final ArrayList<T> results = new ArrayList<>();
if (origList == null)
origList = new ArrayList<>(itemList);
if (constraint != null && constraint.length() > 0) {
if (origList != null && origList.size() > 0) {
for (final T cd : origList) {
if (cd.getAttributeToSearch().toLowerCase()
.contains(constraint.toString().toLowerCase()))
results.add(cd);
}
}
oReturn.values = results;
oReturn.count = results.size();//newly Aded by ZA
} else {
oReturn.values = origList;
oReturn.count = origList.size();//newly added by ZA
}
return oReturn;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(final CharSequence constraint,
FilterResults results) {
itemList = new ArrayList<>((ArrayList<T>) results.values);
// FIXME: 8/16/2017 implement Comparable with sort below
///Collections.sort(itemList);
notifyDataSetChanged();
}
};
}
,其中
public GenericBaseAdapter(Context mContext, List<T> itemList) {
this.mContext = mContext;
this.itemList = itemList;
this.origList = itemList;
}
答案 4 :(得分:1)
通过使用 LiveData 使用 Android体系结构组件,可以使用任何类型的 Adapter 轻松实现。您只需要执行以下步骤:
1。。将数据设置为以 LiveData 的形式从房间 数据库返回,如下例所示:
@Dao
public interface CustomDAO{
@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
public LiveData<List<Word>> searchFor(String searchquery);
}
2。。创建一个 ViewModel 对象,以通过将 DAO 和 UI < / em>
public class CustomViewModel extends AndroidViewModel {
private final AppDatabase mAppDatabase;
public WordListViewModel(@NonNull Application application) {
super(application);
this.mAppDatabase = AppDatabase.getInstance(application.getApplicationContext());
}
public LiveData<List<Word>> searchQuery(String query) {
return mAppDatabase.mWordDAO().searchFor(query);
}
}
3。,通过如下所示通过onQueryTextListener传递查询,从 ViewModel 即时调用数据:
在onCreateOptionsMenu
内部,按如下所示设置您的监听器
searchView.setOnQueryTextListener(onQueryTextListener);
按如下所示在SearchActivity类中的某处设置查询侦听器
private android.support.v7.widget.SearchView.OnQueryTextListener onQueryTextListener =
new android.support.v7.widget.SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
getResults(query);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
getResults(newText);
return true;
}
private void getResults(String newText) {
String queryText = "%" + newText + "%";
mCustomViewModel.searchQuery(queryText).observe(
SearchResultsActivity.this, new Observer<List<Word>>() {
@Override
public void onChanged(@Nullable List<Word> words) {
if (words == null) return;
searchAdapter.submitList(words);
}
});
}
};
注意:步骤(1.)和(2.)是标准的 AAC ViewModel 和 DAO 实现,是唯一真正的“魔术”接下来是 OnQueryTextListener ,它将随着查询文本的更改动态更新列表的结果。
如果您需要对此事进行更多说明,请随时提出。 希望对您有所帮助:)。
答案 5 :(得分:1)
我不知道为什么每个人都使用同一列表的2个副本来解决此问题。这会占用过多的RAM ...
为什么不只是隐藏未找到的元素,而只是将它们的索引存储在Set
中以便以后恢复它们呢?这要少得多的RAM,特别是如果您的对象很大的话。
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.SampleViewHolders>{
private List<MyObject> myObjectsList; //holds the items of type MyObject
private Set<Integer> foundObjects; //holds the indices of the found items
public MyRecyclerViewAdapter(Context context, List<MyObject> myObjectsList)
{
this.myObjectsList = myObjectsList;
this.foundObjects = new HashSet<>();
//first, add all indices to the indices set
for(int i = 0; i < this.myObjectsList.size(); i++)
{
this.foundObjects.add(i);
}
}
@NonNull
@Override
public SampleViewHolders onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.my_layout_for_staggered_grid, null);
MyRecyclerViewAdapter.SampleViewHolders rcv = new MyRecyclerViewAdapter.SampleViewHolders(layoutView);
return rcv;
}
@Override
public void onBindViewHolder(@NonNull SampleViewHolders holder, int position)
{
//look for object in O(1) in the indices set
if(!foundObjects.contains(position))
{
//object not found => hide it.
holder.hideLayout();
return;
}
else
{
//object found => show it.
holder.showLayout();
}
//holder.imgImageView.setImageResource(...)
//holder.nameTextView.setText(...)
}
@Override
public int getItemCount() {
return myObjectsList.size();
}
public void findObject(String text)
{
//look for "text" in the objects list
for(int i = 0; i < myObjectsList.size(); i++)
{
//if it's empty text, we want all objects, so just add it to the set.
if(text.length() == 0)
{
foundObjects.add(i);
}
else
{
//otherwise check if it meets your search criteria and add it or remove it accordingly
if (myObjectsList.get(i).getName().toLowerCase().contains(text.toLowerCase()))
{
foundObjects.add(i);
}
else
{
foundObjects.remove(i);
}
}
}
notifyDataSetChanged();
}
public class SampleViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener
{
public ImageView imgImageView;
public TextView nameTextView;
private final CardView layout;
private final CardView.LayoutParams hiddenLayoutParams;
private final CardView.LayoutParams shownLayoutParams;
public SampleViewHolders(View itemView)
{
super(itemView);
itemView.setOnClickListener(this);
imgImageView = (ImageView) itemView.findViewById(R.id.some_image_view);
nameTextView = (TextView) itemView.findViewById(R.id.display_name_textview);
layout = itemView.findViewById(R.id.card_view); //card_view is the id of my androidx.cardview.widget.CardView in my xml layout
//prepare hidden layout params with height = 0, and visible layout params for later - see hideLayout() and showLayout()
hiddenLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
hiddenLayoutParams.height = 0;
shownLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public void onClick(View view)
{
//implement...
}
private void hideLayout() {
//hide the layout
layout.setLayoutParams(hiddenLayoutParams);
}
private void showLayout() {
//show the layout
layout.setLayoutParams(shownLayoutParams);
}
}
}
我只是将EditText
作为搜索框:
cardsSearchTextView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
myViewAdapter.findObject(editable.toString().toLowerCase());
}
});
结果:
答案 6 :(得分:0)
我建议修改@Xaver Kapeller的解决方案,下面有两件事,以避免在清除搜索到的文本后出现问题(过滤器不再工作),因为适配器的列表后面的大小比过滤器列表小并且发生了IndexOutOfBoundsException。所以代码需要修改如下
public void addItem(int position, ExampleModel model) {
if(position >= mModel.size()) {
mModel.add(model);
notifyItemInserted(mModel.size()-1);
} else {
mModels.add(position, model);
notifyItemInserted(position);
}
}
并在moveItem功能中进行修改
public void moveItem(int fromPosition, int toPosition) {
final ExampleModel model = mModels.remove(fromPosition);
if(toPosition >= mModels.size()) {
mModels.add(model);
notifyItemMoved(fromPosition, mModels.size()-1);
} else {
mModels.add(toPosition, model);
notifyItemMoved(fromPosition, toPosition);
}
}
希望它可以帮到你!
答案 7 :(得分:0)
这是我对扩展@klimat答案以不丢失过滤动画的看法。
public void filter(String query){
int completeListIndex = 0;
int filteredListIndex = 0;
while (completeListIndex < completeList.size()){
Movie item = completeList.get(completeListIndex);
if(item.getName().toLowerCase().contains(query)){
if(filteredListIndex < filteredList.size()) {
Movie filter = filteredList.get(filteredListIndex);
if (!item.getName().equals(filter.getName())) {
filteredList.add(filteredListIndex, item);
notifyItemInserted(filteredListIndex);
}
}else{
filteredList.add(filteredListIndex, item);
notifyItemInserted(filteredListIndex);
}
filteredListIndex++;
}
else if(filteredListIndex < filteredList.size()){
Movie filter = filteredList.get(filteredListIndex);
if (item.getName().equals(filter.getName())) {
filteredList.remove(filteredListIndex);
notifyItemRemoved(filteredListIndex);
}
}
completeListIndex++;
}
}
基本上,它的工作是浏览完整的列表,然后逐项添加/删除项目到过滤后的列表中。
答案 8 :(得分:0)
在适配器中添加接口。
public interface SelectedUser{
void selectedUser(UserModel userModel);
}
在您的mainactivity中实现该接口并覆盖该方法。 @Override 公共无效selectedUser(UserModel userModel){
startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));
}
答案 9 :(得分:0)
如果您想在按钮点击时进行搜索,那么这可以正常工作。
filterIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String strCHR = homeSearchEdit.getText().toString();
if (homeSearchEdit.getText().toString().length() > 0) {
ArrayList<ServiceModel> listNew = new ArrayList<>();
for (int l = 0; l < arrayList.size(); l++) {
String serviceName = arrayList.get(l).getServiceName().toLowerCase();
if (serviceName.contains(strCHR.toLowerCase())) {
listNew.add(arrayList.get(l));
}
}
recyclerView.setVisibility(View.VISIBLE);
adapter = new ServiceAdapter(HomeActivity.this, listNew);
recyclerView.setAdapter(adapter);
} else {
recyclerView.setVisibility(View.VISIBLE);
adapter = new ServiceAdapter(HomeActivity.this, arrayList);
recyclerView.setAdapter(adapter);
}
}
});
where ,filterIcon 是按钮,homeSearchEdit 是 editText(我们申请搜索的地方)。>
答案 10 :(得分:-1)
我使用链接进行了一些修改,解决了同样的问题。 Search filter on RecyclerView with Cards. Is it even possible?(希望这会有所帮助)。
这是我的适配器类
public class ContactListRecyclerAdapter extends RecyclerView.Adapter<ContactListRecyclerAdapter.ContactViewHolder> implements Filterable {
Context mContext;
ArrayList<Contact> customerList;
ArrayList<Contact> parentCustomerList;
public ContactListRecyclerAdapter(Context context,ArrayList<Contact> customerList)
{
this.mContext=context;
this.customerList=customerList;
if(customerList!=null)
parentCustomerList=new ArrayList<>(customerList);
}
// other overrided methods
@Override
public Filter getFilter() {
return new FilterCustomerSearch(this,parentCustomerList);
}
}
//过滤类
import android.widget.Filter;
import java.util.ArrayList;
public class FilterCustomerSearch extends Filter
{
private final ContactListRecyclerAdapter mAdapter;
ArrayList<Contact> contactList;
ArrayList<Contact> filteredList;
public FilterCustomerSearch(ContactListRecyclerAdapter mAdapter,ArrayList<Contact> contactList) {
this.mAdapter = mAdapter;
this.contactList=contactList;
filteredList=new ArrayList<>();
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
filteredList.clear();
final FilterResults results = new FilterResults();
if (constraint.length() == 0) {
filteredList.addAll(contactList);
} else {
final String filterPattern = constraint.toString().toLowerCase().trim();
for (final Contact contact : contactList) {
if (contact.customerName.contains(constraint)) {
filteredList.add(contact);
}
else if (contact.emailId.contains(constraint))
{
filteredList.add(contact);
}
else if(contact.phoneNumber.contains(constraint))
filteredList.add(contact);
}
}
results.values = filteredList;
results.count = filteredList.size();
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
mAdapter.customerList.clear();
mAdapter.customerList.addAll((ArrayList<Contact>) results.values);
mAdapter.notifyDataSetChanged();
}
}
//活动类
public class HomeCrossFadeActivity extends AppCompatActivity implements View.OnClickListener,OnFragmentInteractionListener,OnTaskCompletedListner
{
Fragment fragment;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_homecrossfadeslidingpane2);CardView mCard;
setContentView(R.layout.your_main_xml);}
//other overrided methods
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
MenuInflater inflater = getMenuInflater();
// Inflate menu to add items to action bar if it is present.
inflater.inflate(R.menu.menu_customer_view_and_search, menu);
// Associate searchable configuration with the SearchView
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView =
(SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setQueryHint("Search Customer");
searchView.setSearchableInfo(
searchManager.getSearchableInfo(getComponentName()));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
if(fragment instanceof CustomerDetailsViewWithModifyAndSearch)
((CustomerDetailsViewWithModifyAndSearch)fragment).adapter.getFilter().filter(newText);
return false;
}
});
return true;
}
}
在OnQueryTextChangeListener()方法中使用适配器。因为我的adpter处于片段状态,所以我将它转换为片段。如果适配器位于您的活动类中,则可以直接使用该适配器。
答案 11 :(得分:-1)
在适配器中:
public void setFilter(List<Channel> newList){
mChannels = new ArrayList<>();
mChannels.addAll(newList);
notifyDataSetChanged();
}
活动:
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
newText = newText.toLowerCase();
ArrayList<Channel> newList = new ArrayList<>();
for (Channel channel: channels){
String channelName = channel.getmChannelName().toLowerCase();
if (channelName.contains(newText)){
newList.add(channel);
}
}
mAdapter.setFilter(newList);
return true;
}
});