介绍
由于您的问题并不清楚,您到底在遇到什么问题,因此我撰写了此快速演练,内容涉及如何实现此功能。如果您仍有问题,请随时提出。
在此GitHub存储库中,我有一个正在讨论的所有示例的工作示例。
如果您想了解更多有关示例项目的信息,请访问项目主页。
无论如何,结果应如下所示:
如果您首先想使用演示应用程序,可以从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
其中包含的,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>
如您所见,我使用数据绑定。如果您从未使用过数据绑定,请不要气disc!它非常简单且功能强大,但是我无法解释它在此答案范围内的工作方式。
这是ViewHolder
该ExampleModel
课程的:
public class ExampleViewHolder extends RecyclerView.ViewHolder {
private final ItemExampleBinding mBinding;
public ExampleViewHolder(ItemExampleBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
public void bind(ExampleModel item) {
mBinding.setModel(item);
}
}
再次没有什么特别的。正如我们在上面的布局xml中定义的那样,它仅使用数据绑定将模型类绑定到该布局。
现在,我们终于可以进入真正有趣的部分:编写适配器。我将跳过的基本实现,Adapter
而是将重点放在与该答案相关的部分上。
但是首先,我们必须谈论一件事:SortedList
课堂。
SortedList
这SortedList
是一个完全令人惊奇的工具,它是该RecyclerView
库的一部分。它负责通知Adapter
有关数据集的更改,并且这样做非常有效。它唯一需要您做的就是指定元素的顺序。为此,您需要实现一种compare()
方法,该方法可以SortedList
像一样比较两个元素Comparator
。但是,不是对a 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
,areContentsTheSame
并且areItemsTheSame
你必须根据类型的对象要显示的内容和顺序,这些对象应该出现在屏幕上实现。
让我们一一介绍这些方法:
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
这是compare()
我之前谈到的方法。在此示例中,我只是将调用传递给Comparator
比较两个模型的a。如果您希望项目以字母顺序出现在屏幕上。该比较器可能如下所示:
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
现在让我们看一下下一个方法:
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
此方法的目的是确定模型的内容是否已更改。在SortedList
使用它来确定是否改变事件需要调用-换句话说,如果RecyclerView
要交叉衰减新旧版本。如果你的模型类有一个正确equals()
和hashCode()
执行通常可以只实现它喜欢上面。如果我们在类中添加equals()
和hashCode()
实现,ExampleModel
则应如下所示:
public class ExampleModel implements SortedListAdapter.ViewModel {
private final long mId;
private final String mText;
public ExampleModel(long id, String text) {
mId = id;
mText = text;
}
public long getId() {
return mId;
}
public String getText() {
return mText;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExampleModel model = (ExampleModel) o;
if (mId != model.mId) return false;
return mText != null ? mText.equals(model.mText) : model.mText == null;
}
@Override
public int hashCode() {
int result = (int) (mId ^ (mId >>> 32));
result = 31 * result + (mText != null ? mText.hashCode() : 0);
return result;
}
}
快速提示:只需按一下按钮,大多数IDE(例如Android Studio,IntelliJ和Eclipse)都具有为您生成equals()
和hashCode()
实现的功能!因此,您不必自己实施它们。在Internet上查找它在IDE中的工作方式!
现在让我们看一下最后一个方法:
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
在SortedList
使用此方法来检查,如果两个项目指的是同样的事情。用最简单的术语(无需解释其SortedList
工作原理)来确定是否已包含对象,List
以及是否需要播放添加,移动或更改动画。如果您的模型有一个ID,通常在此方法中仅比较ID。如果不是这样,则您需要找出其他方法来进行检查,但是最终还是要根据您的特定应用程序来实现。通常,给所有模型一个ID是最简单的选择-例如,如果您要查询数据库中的数据,则可以将其作为主键字段。
有了SortedList.Callback
正确实施,我们可以创建的一个实例SortedList
:
final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);
作为构造函数中的第一个参数,SortedList
您需要传递模型的类。另一个参数就是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();
}
我们不需要在这里调用任何notify方法,因为SortedList
已经通过SortedList.Callback
!做到了。除此之外,这些方法的实现非常简单,只有一个例外:remove方法用于删除List
模型。由于SortedList
只有一个remove方法可以删除单个对象,因此我们需要遍历列表并逐个删除模型。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
所有可能模型的a。对于这个例子中我创建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
S以及查询字符串。然后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()
所有字符串进行比较,我们可以确保无论大小写均返回相同的结果。然后,它仅遍历List
传入的所有模型,并检查查询字符串是否包含在模型文本中。如果是,则将模型添加到已过滤的中List
。
就是这样!上面的代码将在API级别7和更高级别上运行,从API级别11开始,您将免费获得道具动画!
我意识到这是一个非常详细的描述,可能使得这整个事情看起来比它确实是比较复杂的,但我们可以概括这整个问题,使实施方式的Adapter
基础上SortedList
要简单得多。
概括问题并简化适配器
在本节中,我将不做详细介绍-部分是因为我在堆栈溢出问题上遇到了字符数限制的问题,还因为上面已经解释了大多数问题-但总结了这些变化:我们可以实现一个基Adapter
类已经负责处理实例和SortedList
绑定模型的ViewHolder
实例,并提供了一种方便的方式实现Adapter
基于的实例SortedList
。为此,我们必须做两件事:
- 我们需要创建一个
ViewModel
接口,所有模型类都必须实现
- 我们需要创建一个
ViewHolder
子类,该子类定义可用于自动绑定模型的bind()
方法Adapter
。
这允许我们仅RecyclerView
通过实现模型及其相应的ViewHolder
实现,专注于应该在中显示的内容。使用此基类,我们不必担心的详细信息Adapter
及其SortedList
。
SortedListAdapter
由于StackOverflow上答案的字符数限制,我无法遍历实现该基类的每个步骤,甚至无法在此处添加完整的源代码,但是您可以在此找到该基类的完整源代码-我称它为SortedListAdapter
- GitHub Gist。
为了简化您的生活,我在jCenter上发布了一个包含SortedListAdapter
!的库。如果要使用它,则只需将此依赖项添加到应用程序的build.gradle文件中:
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
您可以在图书馆主页上找到有关此图书馆的更多信息。
使用SortedListAdapter
要使用,SortedListAdapter
我们必须进行两项更改:
更改ViewHolder
使其扩展SortedListAdapter.ViewHolder
。ViewHolder
在这种情况下,类型参数应该是应与此绑定的模型ExampleModel
。您必须将数据绑定到模型中,performBind()
而不是bind()
。
public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> {
private final ItemExampleBinding mBinding;
public ExampleViewHolder(ItemExampleBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
@Override
protected void performBind(ExampleModel item) {
mBinding.setModel(item);
}
}
确保所有模型都实现该ViewModel
接口:
public class ExampleModel implements SortedListAdapter.ViewModel {
...
}
之后,我们只需要更新ExampleAdapter
即可扩展SortedListAdapter
并删除不再需要的所有内容。在这种情况下,type参数应该是您正在使用的模型的类型ExampleModel
。但是,如果您使用的是不同类型的模型,则将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);
}
}
之后,我们就完成了!然而最后一件事提:在SortedListAdapter
不具有相同的add()
,remove()
或replaceAll()
方法,我们原来的ExampleAdapter
了。它使用一个单独的Editor
对象来修改列表中可以通过该edit()
方法访问的项目。因此,如果要删除或添加项目,则必须调用该对象,edit()
然后在此Editor
实例上添加和删除项目,完成后,请commit()
对其调用以将更改应用于SortedList
:
mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();
您以这种方式进行的所有更改都将一起批处理以提高性能。replaceAll()
我们在以上各章中实现的方法也存在于此Editor
对象上:
mAdapter.edit()
.replaceAll(mModels)
.commit();
如果您忘了打电话,commit()
那么您的任何更改都不会被应用!