[英]How to filter a RecyclerView with a SearchView
I am trying to implement the SearchView
from the support library.我正在尝试从支持库中实现
SearchView
。 I want the user to be to use the SearchView
to filter a List
of movies in a RecyclerView
.我希望用户使用
SearchView
过滤RecyclerView
中的电影List
。
I have followed a few tutorials so far and I have added the SearchView
to the ActionBar
, but I am not really sure where to go from here.到目前为止,我已经学习了一些教程,并将
SearchView
添加到了ActionBar
,但我不确定从这里去哪里。 I have seen a few examples but none of them show results as you start typing.我已经看过一些示例,但是当您开始输入时,它们都没有显示结果。
This is my MainActivity
:这是我的
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);
}
}
And this is my Adapter
:这是我的
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);
}
}
}
Since it is not really clear from your question what exactly you are having trouble with, I wrote up this quick walkthrough about how to implement this feature;由于从您的问题中并不清楚您到底遇到了什么问题,因此我编写了有关如何实现此功能的快速演练; if you still have questions feel free to ask.
如果您仍有问题,请随时提出。
I have a working example of everything I am talking about here in this GitHub Repository .我在这个GitHub Repository 中有一个关于我在这里谈论的所有内容的工作示例。
In any case the result should looks something like this:在任何情况下,结果都应该是这样的:
If you first want to play around with the demo app you can install it from the Play Store:如果您首先想使用演示应用程序,您可以从 Play 商店安装它:
Anyway lets get started.无论如何,让我们开始吧。
SearchView
SearchView
In the folder res/menu
create a new file called main_menu.xml
.在文件夹
res/menu
创建一个名为main_menu.xml
的新文件。 In it add an item and set the actionViewClass
to android.support.v7.widget.SearchView
.在其中添加一个项目并将
actionViewClass
设置为android.support.v7.widget.SearchView
。 Since you are using the support library you have to use the namespace of the support library to set the actionViewClass
attribute.由于您使用的是支持库,因此您必须使用支持库的命名空间来设置
actionViewClass
属性。 Your xml file should look something like this:您的 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>
In your Fragment
or Activity
you have to inflate this menu xml like usual, then you can look for the MenuItem
which contains the SearchView
and implement the OnQueryTextListener
which we are going to use to listen for changes to the text entered into the SearchView
:在您的
Fragment
或Activity
您必须像往常一样OnQueryTextListener
此菜单 xml,然后您可以查找包含SearchView
的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;
}
And now the SearchView
is ready to be used.现在
SearchView
可以使用了。 We will implement the filter logic later on in onQueryTextChange()
once we are finished implementing the Adapter
.一旦我们完成了
Adapter
实现,我们稍后将在onQueryTextChange()
实现过滤器逻辑。
Adapter
Adapter
First and foremost this is the model class I am going to use for this example:首先,这是我将用于此示例的模型类:
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;
}
}
It's just your basic model which will display a text in the RecyclerView
.这只是您的基本模型,它将在
RecyclerView
显示文本。 This is the layout I am going to use to display the text:这是我将用于显示文本的布局:
<?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>
As you can see I use Data Binding.如您所见,我使用数据绑定。 If you have never worked with data binding before don't be discouraged!
如果您以前从未使用过数据绑定,请不要气馁! It's very simple and powerful, however I can't explain how it works in the scope of this answer.
它非常简单和强大,但是我无法解释它在这个答案的范围内是如何工作的。
This is the ViewHolder
for the ExampleModel
class:这是
ExampleModel
类的ViewHolder
:
public class ExampleViewHolder extends RecyclerView.ViewHolder {
private final ItemExampleBinding mBinding;
public ExampleViewHolder(ItemExampleBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
public void bind(ExampleModel item) {
mBinding.setModel(item);
}
}
Again nothing special.再次没什么特别的。 It just uses data binding to bind the model class to this layout as we have defined in the layout xml above.
它只是使用数据绑定将模型类绑定到这个布局,正如我们在上面的布局 xml 中定义的那样。
Now we can finally come to the really interesting part: Writing the Adapter.现在我们终于可以来到真正有趣的部分:编写适配器。 I am going to skip over the basic implementation of the
Adapter
and am instead going to concentrate on the parts which are relevant for this answer.我将跳过
Adapter
的基本实现,而是将注意力集中在与此答案相关的部分。
But first there is one thing we have to talk about: The SortedList
class.但首先我们必须谈论一件事:
SortedList
类。
The SortedList
is a completely amazing tool which is part of the RecyclerView
library. SortedList
是一个非常了不起的工具,它是RecyclerView
库的一部分。 It takes care of notifying the Adapter
about changes to the data set and does so it a very efficient way.它负责通知
Adapter
有关数据集的更改,这是一种非常有效的方式。 The only thing it requires you to do is specify an order of the elements.它唯一需要您做的就是指定元素的顺序。 You need to do that by implementing a
compare()
method which compares two elements in the SortedList
just like a Comparator
.您需要通过实现
compare()
方法来做到这一点,该方法像Comparator
一样Comparator
SortedList
两个元素。 But instead of sorting a List
it is used to sort the items in the RecyclerView
!但是不是对
List
进行排序,而是用于对RecyclerView
的项目进行排序!
The SortedList
interacts with the Adapter
through a Callback
class which you have to implement: SortedList
通过您必须实现的Callback
类与Adapter
交互:
private final SortedList.Callback<ExampleModel> mCallback = new SortedList.Callback<ExampleModel>() {
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
mAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
mAdapter.notifyItemRangeChanged(position, count);
}
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
}
In the methods at the top of the callback like onMoved
, onInserted
, etc. you have to call the equivalent notify method of your Adapter
.在回调顶部的方法中,如
onMoved
、 onInserted
等,您必须调用Adapter
的等效通知方法。 The three methods at the bottom compare
, areContentsTheSame
and areItemsTheSame
you have to implement according to what kind of objects you want to display and in what order these objects should appear on the screen.底部的三个方法
compare
, areContentsTheSame
和areItemsTheSame
您必须根据要显示的对象类型以及这些对象在屏幕上出现的顺序来实现。
Let's go through these methods one by one:让我们一一介绍这些方法:
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
This is the compare()
method I talked about earlier.这就是我之前谈到的
compare()
方法。 In this example I am just passing the call to a Comparator
which compares the two models.在这个例子中,我只是将调用传递给
Comparator
两个模型的Comparator
。 If you want the items to appear in alphabetical order on the screen.如果您希望项目按字母顺序出现在屏幕上。 This comparator might look like this:
这个比较器可能如下所示:
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
Now let's take a look at the next method:现在让我们看看下一个方法:
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
The purpose of this method is to determine if the content of a model has changed.此方法的目的是确定模型的内容是否已更改。 The
SortedList
uses this to determine if a change event needs to be invoked - in other words if the RecyclerView
should crossfade the old and new version. SortedList
使用它来确定是否需要调用更改事件 - 换句话说, RecyclerView
是否应该对旧版本和新版本进行淡入淡出。 If you model classes have a correct equals()
and hashCode()
implementation you can usually just implement it like above.如果您的模型类具有正确的
equals()
和hashCode()
实现,您通常可以像上面那样实现它。 If we add an equals()
and hashCode()
implementation to the ExampleModel
class it should look something like this:如果我们向
ExampleModel
类添加一个equals()
和hashCode()
实现,它应该看起来像这样:
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;
}
}
Quick side note: Most IDE's like Android Studio, IntelliJ and Eclipse have functionality to generate equals()
and hashCode()
implementations for you at the press of a button!快速旁注:大多数 IDE 像 Android Studio、IntelliJ 和 Eclipse 都有功能,只需按一下按钮即可为您生成
equals()
和hashCode()
实现! So you don't have to implement them yourself.所以你不必自己实现它们。 Look up on the internet how it works in your IDE!
在 Internet 上查找它在您的 IDE 中的工作方式!
Now let's take a look at the last method:现在让我们看看最后一个方法:
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
The SortedList
uses this method to check if two items refer to the same thing. SortedList
使用此方法来检查两个项目是否引用同一事物。 In simplest terms (without explaining how the SortedList
works) this is used to determine if an object is already contained in the List
and if either an add, move or change animation needs to be played.用最简单的术语(不解释
SortedList
工作原理),这用于确定对象是否已包含在List
以及是否需要播放添加、移动或更改动画。 If your models have an id you would usually compare just the id in this method.如果你的模型有一个 id,你通常会在这个方法中只比较 id。 If they don't you need to figure out some other way to check this, but however you end up implementing this depends on your specific app.
如果他们不需要,您需要找出其他方法来检查这一点,但最终实现这取决于您的特定应用程序。 Usually it is the simplest option to give all models an id - that could for example be the primary key field if you are querying the data from a database.
通常,给所有模型一个 id 是最简单的选择 - 例如,如果您从数据库中查询数据,它可能是主键字段。
With the SortedList.Callback
correctly implemented we can create an instance of the SortedList
:正确实现
SortedList.Callback
,我们可以创建SortedList
的实例:
final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);
As the first parameter in the constructor of the SortedList
you need to pass the class of your models.作为
SortedList
构造函数中的第一个参数,您需要传递模型的类。 The other parameter is just the SortedList.Callback
we defined above.另一个参数就是我们上面定义的
SortedList.Callback
。
Now let's get down to business: If we implement the Adapter
with a SortedList
it should look something like this:现在让我们进入正
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.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();
}
}
The Comparator
used to sort the item is passed in through the constructor so we can use the same Adapter
even if the items are supposed to be displayed in a different order.用于对项目进行排序的
Comparator
是通过构造函数传入的,因此即使项目应该以不同的顺序显示,我们也可以使用相同的Adapter
。
Now we are almost done!现在我们快完成了! But we first need a way to add or remove items to the
Adapter
.但是我们首先需要一种向
Adapter
添加或删除项目的方法。 For this purpose we can add methods to the Adapter
which allow us to add and remove items to the SortedList
:为此,我们可以向
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();
}
We don't need to call any notify methods here because the SortedList
already does this for through the SortedList.Callback
!我们不需要在这里调用任何通知方法,因为
SortedList
已经通过SortedList.Callback
做到了这一点! Aside from that the implementation of these methods is pretty straight forward with one exception: the remove method which removes a List
of models.除此之外,这些方法的实现非常简单,只有一个例外:删除模型
List
的 remove 方法。 Since the SortedList
has only one remove method which can remove a single object we need to loop over the list and remove the models one by one.由于
SortedList
只有一个可以删除单个对象的 remove 方法,我们需要遍历列表并一个一个删除模型。 Calling beginBatchedUpdates()
at the beginning batches all the changes we are going to make to the SortedList
together and improves performance.在开始时调用
beginBatchedUpdates()
将我们将要对SortedList
进行的所有更改批量化并提高性能。 When we call endBatchedUpdates()
the RecyclerView
is notified about all the changes at once.当我们调用
endBatchedUpdates()
,会立即通知RecyclerView
所有更改。
Additionally what you have to understand is that if you add an object to the SortedList
and it is already in the SortedList
it won't be added again.此外,您必须了解的是,如果您将一个对象添加到
SortedList
并且它已经在SortedList
,则不会再次添加它。 Instead the SortedList
uses the areContentsTheSame()
method to figure out if the object has changed - and if it has the item in the RecyclerView
will be updated.相反,
SortedList
使用areContentsTheSame()
方法来确定对象是否已更改 - 以及它是否具有RecyclerView
的项目将被更新。
Anyway, what I usually prefer is one method which allows me to replace all items in the RecyclerView
at once.无论如何,我通常更喜欢一种方法,它允许我一次替换
RecyclerView
中的所有项目。 Remove everything which is not in the List
and add all items which are missing from the SortedList
:删除不在
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();
}
This method again batches all updates together to increase performance.此方法再次将所有更新一起批处理以提高性能。 The first loop is in reverse since removing an item at the start would mess up the indexes of all items that come up after it and this can lead in some instances to problems like data inconsistencies.
第一个循环是相反的,因为在开始时删除一个项目会弄乱它之后出现的所有项目的索引,这在某些情况下会导致数据不一致等问题。 After that we just add the
List
to the SortedList
using addAll()
to add all items which are not already in the SortedList
and - just like I described above - update all items that are already in the SortedList
but have changed.之后,我们只需使用
addAll()
将List
添加到SortedList
以添加所有不在SortedList
中的项目,并且 - 就像我上面描述的那样 - 更新已经在SortedList
但已更改的所有项目。
And with that the Adapter
is complete.这样
Adapter
就完成了。 The whole thing should look something like this:整个事情应该是这样的:
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();
}
}
The only thing missing now is to implement the filtering!现在唯一缺少的是实现过滤!
To implement the filter logic we first have to define a List
of all possible models.要实现过滤器逻辑,我们首先必须定义所有可能模型的
List
。 For this example I create a List
of ExampleModel
instances from an array of movies:在这个例子中,我从一个电影数组中创建了一个
ExampleModel
实例List
:
private static final String[] MOVIES = new String[]{
...
};
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
private ExampleAdapter mAdapter;
private List<ExampleModel> mModels;
private RecyclerView mRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mAdapter = new ExampleAdapter(this, ALPHABETICAL_COMPARATOR);
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
mBinding.recyclerView.setAdapter(mAdapter);
mModels = new ArrayList<>();
for (String movie : MOVIES) {
mModels.add(new ExampleModel(movie));
}
mAdapter.add(mModels);
}
Nothing special going on here, we just instantiate the Adapter
and set it to the RecyclerView
.这里没什么特别的,我们只是实例化
Adapter
并将其设置为RecyclerView
。 After that we create a List
of models from the movie names in the MOVIES
array.之后,我们从
MOVIES
数组中的电影名称创建一个模型List
。 Then we add all the models to the SortedList
.然后我们将所有模型添加到
SortedList
。
Now we can go back to onQueryTextChange()
which we defined earlier and start implementing the filter logic:现在我们可以回到我们之前定义的
onQueryTextChange()
并开始实现过滤器逻辑:
@Override
public boolean onQueryTextChange(String query) {
final List<ExampleModel> filteredModelList = filter(mModels, query);
mAdapter.replaceAll(filteredModelList);
mBinding.recyclerView.scrollToPosition(0);
return true;
}
This is again pretty straight forward.这再次非常简单。 We call the method
filter()
and pass in the List
of ExampleModel
s as well as the query string.我们调用方法
filter()
并传入ExampleModel
的List
以及查询字符串。 We then call replaceAll()
on the Adapter
and pass in the filtered List
returned by filter()
.然后我们在
Adapter
上调用replaceAll()
并传入filter()
返回的过滤List
。 We also have to call scrollToPosition(0)
on the RecyclerView
to ensure that the user can always see all items when searching for something.我们还必须在
RecyclerView
上调用scrollToPosition(0)
以确保用户在搜索某些内容时始终可以看到所有项目。 Otherwise the RecyclerView
might stay in a scrolled down position while filtering and subsequently hide a few items.否则
RecyclerView
在过滤时可能会停留在向下滚动的位置并随后隐藏一些项目。 Scrolling to the top ensures a better user experience while searching.滚动到顶部可确保在搜索时获得更好的用户体验。
The only thing left to do now is to implement filter()
itself:现在唯一要做的就是实现
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;
}
The first thing we do here is call toLowerCase()
on the query string.我们在这里做的第一件事是在查询字符串上调用
toLowerCase()
。 We don't want our search function to be case sensitive and by calling toLowerCase()
on all strings we compare we can ensure that we return the same results regardless of case.我们不希望我们的搜索函数区分大小写,通过对所有比较的字符串调用
toLowerCase()
,我们可以确保无论大小写都返回相同的结果。 It then just iterates through all the models in the List
we passed into it and checks if the query string is contained in the text of the model.然后它只是遍历我们传递给它的
List
中的所有模型,并检查查询字符串是否包含在模型的文本中。 If it is then the model is added to the filtered List
.如果是,则将模型添加到过滤后的
List
。
And that's it!就是这样! The above code will run on API level 7 and above and starting with API level 11 you get item animations for free!
上面的代码将在 API 级别 7 及更高级别上运行,从 API 级别 11 开始,您可以免费获得项目动画!
I realize that this is a very detailed description which probably makes this whole thing seem more complicated than it really is, but there is a way we can generalize this whole problem and make implementing an Adapter
based on a SortedList
much simpler.我意识到这是一个非常详细的描述,这可能使整个事情看起来比实际更复杂,但是有一种方法可以概括整个问题并使基于
SortedList
的Adapter
实现更简单。
In this section I am not going to go into much detail - partly because I am running up against the character limit for answers on Stack Overflow but also because most of it already explained above - but to summarize the changes: We can implemented a base Adapter
class which already takes care of dealing with the SortedList
as well as binding models to ViewHolder
instances and provides a convenient way to implement an Adapter
based on a SortedList
.在本节中,我不会详细介绍 - 部分是因为我遇到了 Stack Overflow 答案的字符限制,也因为上面已经解释了大部分内容 - 但总结一下变化:我们可以实现一个基本的
Adapter
类已经负责处理SortedList
以及将模型绑定到ViewHolder
实例,并提供了一种方便的方法来实现基于SortedList
的Adapter
。 For that we have to do two things:为此,我们必须做两件事:
ViewModel
interface which all model classes have to implementViewModel
接口ViewHolder
subclass which defines a bind()
method the Adapter
can use to bind models automatically.ViewHolder
子类,它定义了Adapter
可以用来自动绑定模型的bind()
方法。 This allows us to just focus on the content which is supposed to be displayed in the RecyclerView
by just implementing the models and there corresponding ViewHolder
implementations.这允许我们只通过实现模型和相应的
ViewHolder
实现来专注于应该在RecyclerView
显示的内容。 Using this base class we don't have to worry about the intricate details of the Adapter
and its SortedList
.使用这个基类,我们不必担心
Adapter
及其SortedList
的复杂细节。
Because of the character limit for answers on StackOverflow I can't go through each step of implementing this base class or even add the full source code here, but you can find the full source code of this base class - I called it SortedListAdapter
- in this GitHub Gist .由于 StackOverflow 上答案的字符限制,我无法完成实现此基类的每个步骤,甚至无法在此处添加完整的源代码,但您可以找到此基类的完整源代码 - 我将其称为
SortedListAdapter
- 在这个GitHub 要点。
To make your life simple I have published a library on jCenter which contains the SortedListAdapter
!为了让您的生活更简单,我在 jCenter 上发布了一个包含
SortedListAdapter
的库! If you want to use it then all you need to do is add this dependency to your app's build.gradle file:如果您想使用它,那么您需要做的就是将此依赖项添加到您的应用程序的 build.gradle 文件中:
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
You can find more information about this library on the library homepage .您可以在图书馆主页上找到有关该图书馆的更多信息。
To use the SortedListAdapter
we have to make two changes:要使用
SortedListAdapter
我们必须进行两项更改:
Change the ViewHolder
so that it extends SortedListAdapter.ViewHolder
.更改
ViewHolder
使其扩展SortedListAdapter.ViewHolder
。 The type parameter should be the model which should be bound to this ViewHolder
- in this case ExampleModel
.类型参数应该是应该绑定到这个
ViewHolder
的模型 - 在这种情况下ExampleModel
。 You have to bind data to your models in performBind()
instead of bind()
.您必须在
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); } }
Make sure that all your models implement the ViewModel
interface:确保您的所有模型都实现了
ViewModel
接口:
public class ExampleModel implements SortedListAdapter.ViewModel { ... }
After that we just have to update the ExampleAdapter
to extend SortedListAdapter
and remove everything we don't need anymore.之后,我们只需更新
ExampleAdapter
以扩展SortedListAdapter
并删除我们不再需要的所有内容。 The type parameter should be the type of model you are working with - in this case ExampleModel
. type 参数应该是您正在使用的模型类型 - 在本例中为
ExampleModel
。 But if you are working with different types of models then set the type parameter to ViewModel
.但是,如果您使用不同类型的模型,则将 type 参数设置为
ViewModel
。
public class ExampleAdapter extends SortedListAdapter<ExampleModel> {
public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
super(context, ExampleModel.class, comparator);
}
@Override
protected ViewHolder<? extends ExampleModel> onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
return new ExampleViewHolder(binding);
}
@Override
protected boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
@Override
protected boolean areItemContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
}
After that we are done!之后我们就完成了! However one last thing to mention: The
SortedListAdapter
does not have the same add()
, remove()
or replaceAll()
methods our original ExampleAdapter
had.然而,最后要提到的一件事是:
SortedListAdapter
没有与我们原来的ExampleAdapter
相同的add()
、 remove()
或replaceAll()
方法。 It uses a separate Editor
object to modify the items in the list which can be accessed through the edit()
method.它使用单独的
Editor
对象来修改可以通过edit()
方法访问的列表中的项目。 So if you want to remove or add items you have to call edit()
then add and remove the items on this Editor
instance and once you are done, call commit()
on it to apply the changes to the SortedList
:因此,如果您想删除或添加项目,您必须调用
edit()
然后添加和删除此Editor
实例上的项目,完成后,对其调用commit()
以将更改应用于SortedList
:
mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();
All changes you make this way are batched together to increase performance.您以这种方式进行的所有更改都将一起批处理以提高性能。 The
replaceAll()
method we implemented in the chapters above is also present on this Editor
object:我们在上面章节中实现的
replaceAll()
方法也存在于这个Editor
对象中:
mAdapter.edit()
.replaceAll(mModels)
.commit();
If you forget to call commit()
then none of your changes will be applied!如果您忘记调用
commit()
则不会应用任何更改!
All you need to do is to add filter
method in RecyclerView.Adapter
:您需要做的就是在
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
is initialized in adapter's constructor like itemsCopy.addAll(items)
. itemsCopy
在适配器的构造函数中初始化,如itemsCopy.addAll(items)
。
If you do so, just call filter
from OnQueryTextListener
:如果这样做,只需从
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;
}
});
It's an example from filtering my phonebook by name and phone number.这是按姓名和电话号码过滤我的电话簿的示例。
Following @Shruthi Kamoji in a cleaner way, we can just use a filterable, its meant for that:以更简洁的方式关注@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;
}
}
The E here is a Generic Type, you can extend it using your class:这里的 E 是一个泛型类型,你可以使用你的类来扩展它:
public class customerAdapter extends GenericRecycleAdapter<CustomerModel>
Or just change the E to the type you want ( <CustomerModel>
for example)或者只是将 E 更改为您想要的类型(例如
<CustomerModel>
)
Then from searchView (the widget you can put on menu.xml):然后从 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;
}
});
In Adapter:在适配器中:
public void setFilter(List<Channel> newList){
mChannels = new ArrayList<>();
mChannels.addAll(newList);
notifyDataSetChanged();
}
In 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;
}
});
simply create two list in adapter one orignal and one temp and implements Filterable .
只需在适配器中创建两个列表,一个 orignal 和一个 temp 并实现 Filterable 。
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
final FilterResults oReturn = new FilterResults();
final ArrayList<T> results = new ArrayList<>();
if (origList == null)
origList = new ArrayList<>(itemList);
if (constraint != null && constraint.length() > 0) {
if (origList != null && origList.size() > 0) {
for (final T cd : origList) {
if (cd.getAttributeToSearch().toLowerCase()
.contains(constraint.toString().toLowerCase()))
results.add(cd);
}
}
oReturn.values = results;
oReturn.count = results.size();//newly Aded by ZA
} else {
oReturn.values = origList;
oReturn.count = origList.size();//newly added by ZA
}
return oReturn;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(final CharSequence constraint,
FilterResults results) {
itemList = new ArrayList<>((ArrayList<T>) results.values);
// FIXME: 8/16/2017 implement Comparable with sort below
///Collections.sort(itemList);
notifyDataSetChanged();
}
};
}
where在哪里
public GenericBaseAdapter(Context mContext, List<T> itemList) {
this.mContext = mContext;
this.itemList = itemList;
this.origList = itemList;
}
With Android Architecture Components through the use of LiveData this can be easily implemented with any type of Adapter .通过使用LiveData 的Android 架构组件,这可以通过任何类型的Adapter轻松实现。 You simply have to do the following steps:
您只需执行以下步骤:
1. Setup your data to return from the Room Database as LiveData as in the example below: 1.设置您的数据从房间数据库作为LiveData返回,如下例所示:
@Dao
public interface CustomDAO{
@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
public LiveData<List<Word>> searchFor(String searchquery);
}
2. Create a ViewModel object to update your data live through a method that will connect your DAO and your UI 2.创建一个ViewModel对象,通过连接DAO和UI的方法实时更新数据
public class CustomViewModel extends AndroidViewModel {
private final AppDatabase mAppDatabase;
public WordListViewModel(@NonNull Application application) {
super(application);
this.mAppDatabase = AppDatabase.getInstance(application.getApplicationContext());
}
public LiveData<List<Word>> searchQuery(String query) {
return mAppDatabase.mWordDAO().searchFor(query);
}
}
3. Call your data from the ViewModel on the fly by passing in the query through onQueryTextListener as below: 3.通过 onQueryTextListener 传入查询,即时从ViewModel调用您的数据,如下所示:
Inside onCreateOptionsMenu
set your listener as follows在
onCreateOptionsMenu
里面设置你的监听器如下
searchView.setOnQueryTextListener(onQueryTextListener);
Setup your query listener somewhere in your SearchActivity class as follows在 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);
}
});
}
};
Note : Steps (1.) and (2.) are standard AAC ViewModel and DAO implementation, the only real "magic" going on here is in the OnQueryTextListener which will update the results of your list dynamically as the query text changes.注意:步骤 (1.) 和 (2.) 是标准的AAC ViewModel和DAO实现,这里唯一真正的“魔法”是在OnQueryTextListener 中,它会随着查询文本的变化动态更新列表的结果。
If you need more clarification on the matter please don't hesitate to ask.如果您需要有关此事的更多说明,请随时提出。 I hope this helped :).
我希望这有帮助:)。
I have solved the same problem using the link with some modifications in it.我已经使用链接解决了同样的问题,并对其进行了一些修改。 Search filter on RecyclerView with Cards.
RecyclerView with Cards 上的搜索过滤器。 Is it even possible?
甚至有可能吗? (hope this helps).
(希望这可以帮助)。
Here is my adapter class这是我的适配器类
public class ContactListRecyclerAdapter extends RecyclerView.Adapter<ContactListRecyclerAdapter.ContactViewHolder> implements Filterable {
Context mContext;
ArrayList<Contact> customerList;
ArrayList<Contact> parentCustomerList;
public ContactListRecyclerAdapter(Context context,ArrayList<Contact> customerList)
{
this.mContext=context;
this.customerList=customerList;
if(customerList!=null)
parentCustomerList=new ArrayList<>(customerList);
}
// other overrided methods
@Override
public Filter getFilter() {
return new FilterCustomerSearch(this,parentCustomerList);
}
}
//Filter class //过滤器类
import android.widget.Filter;
import java.util.ArrayList;
public class FilterCustomerSearch extends Filter
{
private final ContactListRecyclerAdapter mAdapter;
ArrayList<Contact> contactList;
ArrayList<Contact> filteredList;
public FilterCustomerSearch(ContactListRecyclerAdapter mAdapter,ArrayList<Contact> contactList) {
this.mAdapter = mAdapter;
this.contactList=contactList;
filteredList=new ArrayList<>();
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
filteredList.clear();
final FilterResults results = new FilterResults();
if (constraint.length() == 0) {
filteredList.addAll(contactList);
} else {
final String filterPattern = constraint.toString().toLowerCase().trim();
for (final Contact contact : contactList) {
if (contact.customerName.contains(constraint)) {
filteredList.add(contact);
}
else if (contact.emailId.contains(constraint))
{
filteredList.add(contact);
}
else if(contact.phoneNumber.contains(constraint))
filteredList.add(contact);
}
}
results.values = filteredList;
results.count = filteredList.size();
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
mAdapter.customerList.clear();
mAdapter.customerList.addAll((ArrayList<Contact>) results.values);
mAdapter.notifyDataSetChanged();
}
} }
//Activity class //活动类
public class HomeCrossFadeActivity extends AppCompatActivity implements View.OnClickListener,OnFragmentInteractionListener,OnTaskCompletedListner
{
Fragment fragment;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_homecrossfadeslidingpane2);CardView mCard;
setContentView(R.layout.your_main_xml);}
//other overrided methods
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
MenuInflater inflater = getMenuInflater();
// Inflate menu to add items to action bar if it is present.
inflater.inflate(R.menu.menu_customer_view_and_search, menu);
// Associate searchable configuration with the SearchView
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView =
(SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setQueryHint("Search Customer");
searchView.setSearchableInfo(
searchManager.getSearchableInfo(getComponentName()));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
if(fragment instanceof CustomerDetailsViewWithModifyAndSearch)
((CustomerDetailsViewWithModifyAndSearch)fragment).adapter.getFilter().filter(newText);
return false;
}
});
return true;
}
}
In OnQueryTextChangeListener() method use your adapter.在 OnQueryTextChangeListener() 方法中使用您的适配器。 I have casted it to fragment as my adpter is in fragment.
我已将它转换为片段,因为我的适配器在片段中。 You can use the adapter directly if its in your activity class.
如果适配器在您的活动类中,您可以直接使用它。
This is my take on expanding @klimat answer to not losing filtering animation.这是我对扩展@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++;
}
}
Basically what it does is looking through a complete list and adding/removing items to a filtered list one by one.基本上它所做的是查看一个完整的列表并将项目一项一项地添加/删除到过滤列表中。
Add an interface in your adapter.在您的适配器中添加一个接口。
public interface SelectedUser{
void selectedUser(UserModel userModel);
}
implement the interface in your mainactivity and override the method.在您的主要活动中实现接口并覆盖该方法。 @Override public void selectedUser(UserModel userModel) {
@Override public void selectedUser(UserModel userModel) {
startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));
}
Full tutorial and source code: Recyclerview with searchview and onclicklistener完整教程和源代码: Recyclerview with searchview 和 onclicklistener
I don't know why everyone is using 2 copies of the same list to solve this.我不知道为什么每个人都使用同一个列表的 2 个副本来解决这个问题。 This uses too much RAM...
这使用了太多的内存...
Why not just hide the elements that are not found, and simply store their index in a Set
to be able to restore them later?为什么不直接隐藏未找到的元素,并简单地将它们的索引存储在一个
Set
以便以后能够恢复它们? That's much less RAM especially if your objects are quite large. 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);
}
}
}
And I simply have an EditText
as my search box:我只有一个
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());
}
});
Result:结果:
I recommend modify the solution of @Xaver Kapeller with 2 things below to avoid a problem after you cleared the searched text (the filter didn't work anymore) due to the list back of adapter has smaller size than filter list and the IndexOutOfBoundsException happened.我建议使用以下 2 件事修改 @Xaver Kapeller 的解决方案,以避免在清除搜索文本(过滤器不再工作)后出现问题,因为适配器背面的列表大小小于过滤器列表,并且发生了 IndexOutOfBoundsException。 So the code need to modify as below
所以代码需要修改如下
public void addItem(int position, ExampleModel model) {
if(position >= mModel.size()) {
mModel.add(model);
notifyItemInserted(mModel.size()-1);
} else {
mModels.add(position, model);
notifyItemInserted(position);
}
}
And modify also in moveItem functionality并在 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);
}
}
Hope that It could help you!希望它可以帮助你!
If you want to search on button click then this works fine.如果你想搜索按钮点击然后这工作正常。
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 is button and homeSearchEdit is editText(where we apply for search).其中, filterIcon是按钮, homeSearchEdit是 editText(我们申请搜索的地方)。
Android has provided DiffUtil.Callback()
and DiffUtil.ItemCallback<T>
and they help us filter our recycler view nicely Android 提供了
DiffUtil.Callback()
和DiffUtil.ItemCallback<T>
,它们帮助我们很好地过滤我们的回收器视图
DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one.
DiffUtil 是一个实用程序类,它计算两个列表之间的差异并输出一个更新操作列表,将第一个列表转换为第二个列表。
https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil
https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil
DiffUtil.Callback() is used with RecyclerView.Adapter DiffUtil.Callback() 与 RecyclerView.Adapter 一起使用
and和
DiffUtil.ItemCallback is used with ListAdapter DiffUtil.ItemCallback 与 ListAdapter 一起使用
Create your RecyclerView Like you normally would overriding the创建你的 RecyclerView 就像你通常会覆盖
onCreateViewHolder
onBindViewHolder
getItemCount
and extending the RecyclerView.ViewHolder Class
并扩展
RecyclerView.ViewHolder Class
Just Like you have done(This is the Kotlin version of snippets from your code)就像您所做的一样(这是您的代码片段的 Kotlin 版本)
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder? {
val v: View = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.recycler_view_card_item, viewGroup, false)
return ViewHolder(v)
}
fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val movie: Movie = mItems.get(i)
viewHolder.tvMovie.setText(movie.getName())
viewHolder.tvMovieRating.setText(movie.getRating())
}
override fun getItemCount(): Int {
return mItems.size()
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvMovie: TextView
var tvMovieRating: TextView
init {
tvMovie = itemView.findViewById<View>(R.id.movieName) as TextView
tvMovieRating = itemView.findViewById<View>(R.id.movieRating) as TextView
}
}
Now Create another class which will implement the DiffUtil.Callback()现在创建另一个将实现 DiffUtil.Callback() 的类
This class will help convert the recyclerviews currentlist to the filtered list此类将帮助将 recyclerviews currentlist 转换为过滤后的列表
class MoviesDiffUtilCallback(private val oldList: List<Movies>, private val newList: List<Movies>) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = oldList[oldItemPosition].aUniqueId == newList[newItemPosition]. aUniqueId
//aUniqueId-> a field that is unique to each item in your listItems
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = oldList[oldItemPosition] == newList[newItemPosition]
}
In your Activity or Fragment Class Setup your adapter and your filter在您的 Activity 或 Fragment 类中设置您的适配器和过滤器
private fun setupAdapter() {
//mItems is the list you will pass to the adapter
adapter = CardAdapter(mItems)
recyclerView.adapter = adapter
}
fun filter(searchText : String){
val newFilter = mItems.filter {
it.name.lowercase().contains(text.lowercase()) //filterlogic
}
//Calculate the list of update operations that can covert one list into the other one
val diffResult = DiffUtil.calculateDiff(PostsDiffUtilCallback(mItems,newFilter))
mItems.clear()
mItems.addAll(newFilter)
//dispatch all updates to the RecyclerView
diffResult.dispatchUpdatesTo(adapter)
}
We will be using the filterable interface to help us filter (still figuring out why I shouldn't just use a filter function to get filteredLists and submitList(filteredLists) Directly)我们将使用 filterable 接口来帮助我们进行过滤(仍在弄清楚为什么我不应该只使用过滤器函数来获取过滤列表和 submitList(filteredLists) 直接)
Create your ListAdapter class创建您的 ListAdapter 类
class CardAdapter (
private val mItems : List<Movies>) : ListAdapter<Movies, CardAdapter.BillsPackageViewHolder>(MoviesDiffCallback()),
Filterable {
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder? {
val v: View = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.recycler_view_card_item, viewGroup, false)
return ViewHolder(v)
}
fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val movie: Movie = mItems.get(i)
viewHolder.tvMovie.setText(movie.getName())
viewHolder.tvMovieRating.setText(movie.getRating())
}
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
return FilterResults().apply {
values = if (constraint.isNullOrEmpty())
mItems
else
onFilter(mItems, constraint.toString())
}
}
@Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
submitList(results?.values as? List<Movies>)
}
}
}
fun onFilter(list: List<Movies>, constraint: String) : List<Movies>{
val filteredList = list.filter {
it.name.lowercase().contains(constraint.lowercase())
}
return filteredList
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvMovie: TextView
var tvMovieRating: TextView
init {
tvMovie = itemView.findViewById<View>(R.id.movieName) as TextView
tvMovieRating = itemView.findViewById<View>(R.id.movieRating) as TextView
}
}
}
Now Create another class which will implement the DiffUtil.ItemCallback现在创建另一个将实现 DiffUtil.ItemCallback 的类
class MoviesDiffCallback : DiffUtil.ItemCallback<Movies>() {
override fun areItemsTheSame(oldItem: Movies, newItem: Movies): Boolean {
return oldItem.someUniqueid == newItem.someUniqueid
}
override fun areContentsTheSame(oldItem: Movies, newItem: Movies): Boolean {
return oldItem == newItem
}
}
and in your MainActivity or Fragment Setup your adapter and your filter在您的 MainActivity 或 Fragment Setup 中,您的适配器和过滤器
private fun setupAdapter() {
adapter = CardAdapter(mItems)
recyclerView.adapter = adapter
}
fun filter(searchString : String){
adapter.filter.filter(searchString)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.