如何使用 SearchView 过滤 RecyclerView [英] How to filter a RecyclerView with a SearchView

查看:48
本文介绍了如何使用 SearchView 过滤 RecyclerView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试从支持库中实现 SearchView.我希望用户使用 SearchView 过滤 RecyclerView 中的 List 电影.

到目前为止,我已经学习了一些教程,并且已将 SearchView 添加到 ActionBar,但我不确定从哪里开始.我看过一些例子,但当你开始输入时,它们都没有显示结果.

这是我的MainActivity:

公共类 MainActivity 扩展 ActionBarActivity {RecyclerView mRecyclerView;RecyclerView.LayoutManager mLayoutManager;RecyclerView.Adapter mAdapter;@覆盖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 = 新 CardAdapter() {@覆盖公共过滤器 getFilter() {返回空;}};mRecyclerView.setAdapter(mAdapter);}@覆盖公共布尔 onCreateOptionsMenu(菜单菜单){//给菜单充气;如果它存在,这会将项目添加到操作栏.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()));返回真;}@覆盖公共布尔 onOptionsItemSelected(MenuItem item) {//在此处处理操作栏项目的点击.操作栏将//自动处理点击 Home/Up 按钮,这么久//当您在 AndroidManifest.xml 中指定父活动时.int id = item.getItemId();//不检查SimplifiableIfStatement如果(id == R.id.action_settings){返回真;}返回 super.onOptionsItemSelected(item);}}

这是我的Adapter:

公共抽象类 CardAdapter extends RecyclerView.Adapter实现可过滤{列表<电影>mItems;公共卡适配器(){极好的();mItems = new ArrayList();电影电影 = 新电影();电影.setName("蜘蛛侠");电影.setRating("92");mItems.add(电影);电影 = 新电影();movie.setName("毁灭战士 3");电影.setRating("91");mItems.add(电影);电影 = 新电影();电影.setName("变形金刚");电影.setRating("88");mItems.add(电影);电影 = 新电影();movie.setName("变形金刚2");电影.setRating("87");mItems.add(电影);电影 = 新电影();movie.setName("变形金刚3");电影.setRating("86");mItems.add(电影);电影 = 新电影();电影.setName("诺亚");电影.setRating("86");mItems.add(电影);电影 = 新电影();movie.setName("钢铁侠");电影.setRating("86");mItems.add(电影);电影 = 新电影();movie.setName("钢铁侠 2");电影.setRating("86");mItems.add(电影);电影 = 新电影();movie.setName("钢铁侠 3");电影.setRating("86");mItems.add(电影);}@覆盖公共 ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);返回新的 ViewHolder(v);}@覆盖公共无效 onBindViewHolder(ViewHolder viewHolder, int i) {电影电影 = mItems.get(i);viewHolder.tvMovie.setText(movie.getName());viewHolder.tvMovieRating.setText(movie.getRating());}@覆盖公共 int getItemCount() {返回 mItems.size();}类 ViewHolder 扩展 RecyclerView.ViewHolder{公共 TextView tvMovie;公共 TextView tvMovieRating;公共 ViewHolder(查看 itemView){超级(项目视图);tvMovie = (TextView)itemView.findViewById(R.id.movi​​eName);tvMovieRating = (TextView)itemView.findViewById(R.id.movi​​eRating);}}}

解决方案

简介

由于您的问题并不清楚您究竟遇到了什么问题,因此我编写了有关如何实现此功能的快速演练;如果您仍有疑问,请随时提问.

我在这个

无论如何,让我们开始吧.


设置SearchView

在文件夹 res/menu 中创建一个名为 main_menu.xml 的新文件.在其中添加一个项目并将 actionViewClass 设置为 android.support.v7.widget.SearchView.由于您使用的是支持库,因此您必须使用支持库的命名空间来设置 actionViewClass 属性.您的 xml 文件应如下所示:

<item android:id="@+id/action_search";android:title="@string/action_search";app:actionViewClass=android.support.v7.widget.SearchView"app:showAsAction="总是"/></菜单>

在您的 FragmentActivity 中,您必须像往常一样扩充此菜单 xml,然后您可以查找包含 MenuItemMenuItemcode>SearchView 并实现 OnQueryTextListener,我们将使用它来监听输入到 SearchView 中的文本的变化:

@Override公共布尔 onCreateOptionsMenu(菜单菜单){getMenuInflater().inflate(R.menu.menu_main, menu);final MenuItem searchItem = menu.findItem(R.id.action_search);最终 SearchView searchView = (SearchView) searchItem.getActionView();searchView.setOnQueryTextListener(this);返回真;}@覆盖公共布尔 onQueryTextChange(字符串查询){//这里是我们要实现过滤器逻辑的地方返回假;}@覆盖公共布尔 onQueryTextSubmit(字符串查询){返回假;}

现在 SearchView 可以使用了.一旦我们完成了 Adapter 的实现,我们将在稍后的 onQueryTextChange() 中实现过滤器逻辑.


设置Adapter

首先,这是我将用于此示例的模型类:

公共类 ExampleModel {私人最终长 mId;私有最终字符串 mText;公共示例模型(长 ID,字符串文本){mId = id;mText = 文字;}公共长 getId() {返回 mId;}公共字符串 getText() {返回文本;}}

这只是您的基本模型,它将在 RecyclerView 中显示文本.这是我将用于显示文本的布局:

<layout xmlns:android="http://schemas.android.com/apk/res/android"><数据><变量名称=型号"type=com.github.wrdlbrnft.searchablerecyclerviewdemo.ui.models.ExampleModel"/></数据><框架布局android:layout_width=match_parent"android:layout_height="wrap_content";android:background="?attr/selectableItemBackground";android:clickable="true"><文本视图android:layout_width=match_parent"android:layout_height="wrap_content";android:padding="8dp"android:text="@{model.text}"/></FrameLayout></布局>

如您所见,我使用数据绑定.如果您以前从未使用过数据绑定,请不要气馁!它非常简单和强大,但是我无法解释它在这个答案的范围内是如何工作的.

这是 ExampleModel 类的 ViewHolder:

public class ExampleViewHolder extends RecyclerView.ViewHolder {私有最终 ItemExampleBinding mBinding;公共 ExampleViewHolder(ItemExampleBinding 绑定) {超级(绑定.getRoot());mBinding = 绑定;}公共无效绑定(示例模型项){mBinding.setModel(item);}}

再次没什么特别的.它只是使用数据绑定将模型类绑定到我们在上面的布局 xml 中定义的这个布局.

现在我们终于可以来到真正有趣的部分:编写适配器.我将跳过 Adapter 的基本实现,而是专注于与此答案相关的部分.

但首先我们要谈一件事:SortedList 类.


排序列表

SortedList 是一个非常了不起的工具,它是 RecyclerView 库的一部分.它负责通知 Adapter 数据集的变化,这是一种非常有效的方式.它需要您做的唯一一件事就是指定元素的顺序.您需要通过实现一个 compare() 方法来做到这一点,该方法就像一个 Comparator 一样比较 SortedList 中的两个元素.但不是对 List 进行排序,而是用于对 RecyclerView 中的项目进行排序!

SortedList 通过您必须实现的 Callback 类与 Adapter 交互:

private final SortedList.CallbackmCallback = new SortedList.Callback() {@覆盖public void onInserted(int position, int count) {mAdapter.notifyItemRangeInserted(position, count);}@覆盖public void onRemoved(int position, int count) {mAdapter.notifyItemRangeRemoved(position, count);}@覆盖public void onMoved(int fromPosition, int toPosition) {mAdapter.notifyItemMoved(fromPosition, toPosition);}@覆盖public void onChanged(int position, int count) {mAdapter.notifyItemRangeChanged(position, count);}@覆盖公共 int 比较(ExampleModel a,ExampleModel b){返回 mComparator.compare(a, b);}@覆盖公共布尔 areContentsTheSame(ExampleModel oldItem,ExampleModel newItem){返回 oldItem.equals(newItem);}@覆盖公共布尔 areItemsTheSame(ExampleModel item1, ExampleModel item2) {返回 item1.getId() == item2.getId();}}

在回调顶部的方法中,例如 onMovedonInserted 等,您必须调用您的 Adapter.最底层的三个方法compareareContentsTheSameareItemsTheSame你要根据你要显示的对象类型和顺序来实现这些对象应该出现在屏幕上.

让我们一一介绍这些方法:

@Override公共 int 比较(ExampleModel a,ExampleModel b){返回 mComparator.compare(a, b);}

这是我之前谈到的 compare() 方法.在这个例子中,我只是将调用传递给一个比较两个模型的 Comparator.如果您希望项目按字母顺序出现在屏幕上.此比较器可能如下所示:

private static final ComparatorALPHABETICAL_COMPARATOR = 新比较器() {@覆盖公共 int 比较(ExampleModel a,ExampleModel b){返回 a.getText().compareTo(b.getText());}};

现在我们来看看下一个方法:

@Override公共布尔 areContentsTheSame(ExampleModel oldItem,ExampleModel newItem){返回 oldItem.equals(newItem);}

此方法的目的是确定模型的内容是否发生了变化.SortedList 使用它来确定是否需要调用更改事件 - 换句话说,RecyclerView 是否应该对旧版本和新版本进行淡入淡出.如果您的模型类具有正确的 equals()hashCode() 实现,您通常可以像上面那样实现它.如果我们向 ExampleModel 类添加一个 equals()hashCode() 实现,它应该看起来像这样:

 公共类 ExampleModel 实现了 SortedListAdapter.ViewModel {私人最终长 mId;私有最终字符串 mText;公共示例模型(长 ID,字符串文本){mId = id;mText = 文字;}公共长 getId() {返回 mId;}公共字符串 getText() {返回文本;}@覆盖公共布尔等于(对象 o){if (this == o) 返回真;if (o == null || getClass() != o.getClass()) 返回假;ExampleModel 模型 = (ExampleModel) o;如果(mId != model.mId)返回假;返回 mText != null ?mText.equals(model.mText) : model.mText == null;}@覆盖公共 int hashCode() {int 结果 = (int) (mId ^ (mId >>> 32));结果 = 31 * 结果 + (mText != null ? mText.hashCode() : 0);返回结果;}}

快速旁注:大多数 IDE(如 Android Studio、IntelliJ 和 Eclipse)都具有为您生成 equals()hashCode() 实现的功能,只需按一下按钮!所以你不必自己实现它们.在互联网上查看它在您的 IDE 中的工作原理!

现在我们来看看最后一种方法:

@Override公共布尔 areItemsTheSame(ExampleModel item1, ExampleModel item2) {返回 item1.getId() == item2.getId();}

SortedList 使用此方法来检查两个项目是否引用同一事物.用最简单的术语(不解释 SortedList 的工作原理)这用于确定对象是否已包含在 List 中,以及是否需要添加、移动或更改动画被播放.如果你的模型有一个 id,你通常会在这个方法中只比较 id.如果他们不需要,您需要找出其他方法来检查这一点,但是您最终实现这取决于您的特定应用程序.通常,给所有模型一个 id 是最简单的选择 - 例如,如果您从数据库中查询数据,它可以是主键字段.

正确实现 SortedList.Callback 后,我们可以创建 SortedList 的实例:

final SortedListlist = new SortedList<>(ExampleModel.class, mCallback);

作为 SortedList 构造函数中的第一个参数,您需要传递模型的类.另一个参数就是我们上面定义的 SortedList.Callback.

现在让我们进入正题:如果我们使用 SortedList 来实现 Adapter,它应该看起来像这样:

public class ExampleAdapter extends RecyclerView.Adapter{私有最终 SortedListmSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback() {@覆盖公共 int 比较(ExampleModel a,ExampleModel b){返回 mComparator.compare(a, b);}@覆盖public void onInserted(int position, int count) {notifyItemRangeInserted(位置,计数);}@覆盖public void onRemoved(int position, int count) {notifyItemRangeRemoved(位置,计数);}@覆盖public void onMoved(int fromPosition, int toPosition) {notifyItemMoved(fromPosition, toPosition);}@覆盖public void onChanged(int position, int count) {notifyItemRangeChanged(位置,计数);}@覆盖公共布尔 areContentsTheSame(ExampleModel oldItem,ExampleModel newItem){返回 oldItem.equals(newItem);}@覆盖公共布尔 areItemsTheSame(ExampleModel item1, ExampleModel item2) {返回 item1.getId() == item2.getId();}});私人最终 LayoutInflater mInflater;私有最终比较器m比较器;公共示例适配器(上下文上下文,比较器<示例模型>比较器){mInflater = LayoutInflater.from(context);mComparator = 比较器;}@覆盖公共 ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {最终 ItemExampleBinding 绑定 = ItemExampleBinding.inflate(inflater, parent, false);返回新的 ExampleViewHolder(binding);}@覆盖public void onBindViewHolder(ExampleViewHolder holder, int position) {最终 ExampleModel 模型 = mSortedList.get(position);持有人绑定(模型);}@覆盖公共 int getItemCount() {返回 mSortedList.size();}}

用于对项目进行排序的 Comparator 通过构造函数传入,因此我们可以使用相同的 Adapter 即使项目应该以不同的顺序显示.

现在我们快完成了!但是我们首先需要一种向Adapter 添加或删除项目的方法.为此,我们可以向 Adapter 添加方法,允许我们向 SortedList 添加和删除项目:

public void add(ExampleModel model) {mSortedList.add(model);}公共无效删除(示例模型模型){mSortedList.remove(model);}公共无效添加(列表<示例模型>模型){mSortedList.addAll(模型);}公共无效删除(列表<示例模型>模型){mSortedList.beginBatchedUpdates();对于(示例模型模型:模型){mSortedList.remove(model);}mSortedList.endBatchedUpdates();}

我们不需要在这里调用任何通知方法,因为 SortedList 已经通过 SortedList.Callback 做到了这一点!除此之外,这些方法的实现非常简单,只有一个例外:删除模型的 List 的 remove 方法.由于 SortedList 只有一个 remove 方法可以删除单个对象,我们需要遍历列表并一个一个删除模型.在开始时调用 beginBatchedUpdates() 将我们将对 SortedList 进行的所有更改集中在一起并提高性能.当我们调用 endBatchedUpdates() 时,RecyclerView 会立即收到所有更改的通知.

此外,您必须了解的是,如果您将一个对象添加到 SortedList 并且它已经在 SortedList 中,它将不会再次添加.相反,SortedList 使用 areContentsTheSame() 方法来确定对象是否已更改 - 以及它是否在 RecyclerView 中有项目更新了.

无论如何,我通常更喜欢一种方法,它允许我一次替换 RecyclerView 中的所有项目.删除不在 List 中的所有内容并添加 SortedList 中缺少的所有项目:

public void replaceAll(Listmodels) {mSortedList.beginBatchedUpdates();for (int i = mSortedList.size() - 1; i >= 0; i--) {最终 ExampleModel 模型 = mSortedList.get(i);如果(!models.contains(模型)){mSortedList.remove(model);}}mSortedList.addAll(模型);mSortedList.endBatchedUpdates();}

此方法再次将所有更新批量处理以提高性能.第一个循环是相反的,因为在开始时删除一个项目会弄乱它之后出现的所有项目的索引,这在某些情况下会导致数据不一致等问题.之后,我们只需使用 addAll()List 添加到 SortedList 以添加 SortedList 和 - 就像我上面描述的 - 更新 SortedList 中已经存在但已更改的所有项目.

到此,Adapter 就完成了.整个事情应该是这样的:

public class ExampleAdapter extends RecyclerView.Adapter{私有最终 SortedListmSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback() {@覆盖公共 int 比较(ExampleModel a,ExampleModel b){返回 mComparator.compare(a, b);}@覆盖public void onInserted(int position, int count) {notifyItemRangeInserted(位置,计数);}@覆盖public void onRemoved(int position, int count) {notifyItemRangeRemoved(位置,计数);}@覆盖public void onMoved(int fromPosition, int toPosition) {notifyItemMoved(fromPosition, toPosition);}@覆盖public void onChanged(int position, int count) {notifyItemRangeChanged(位置,计数);}@覆盖公共布尔 areContentsTheSame(ExampleModel oldItem,ExampleModel newItem){返回 oldItem.equals(newItem);}@覆盖公共布尔 areItemsTheSame(ExampleModel item1, ExampleModel item2) {返回 item1 == item2;}});私有最终比较器m比较器;私人最终 LayoutInflater mInflater;公共示例适配器(上下文上下文,比较器<示例模型>比较器){mInflater = LayoutInflater.from(context);mComparator = 比较器;}@覆盖公共 ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {最终 ItemExampleBinding 绑定 = ItemExampleBinding.inflate(mInflater, parent, false);返回新的 ExampleViewHolder(binding);}@覆盖public void onBindViewHolder(ExampleViewHolder holder, int position) {最终 ExampleModel 模型 = mSortedList.get(position);持有人绑定(模型);}公共无效添加(示例模型模型){mSortedList.add(model);}公共无效删除(示例模型模型){mSortedList.remove(model);}公共无效添加(列表<示例模型>模型){mSortedList.addAll(模型);}公共无效删除(列表<示例模型>模型){mSortedList.beginBatchedUpdates();对于(示例模型模型:模型){mSortedList.remove(model);}mSortedList.endBatchedUpdates();}public void replaceAll(List模型){mSortedList.beginBatchedUpdates();for (int i = mSortedList.size() - 1; i >= 0; i--) {最终 ExampleModel 模型 = mSortedList.get(i);如果(!models.contains(模型)){mSortedList.remove(model);}}mSortedList.addAll(模型);mSortedList.endBatchedUpdates();}@覆盖公共 int getItemCount() {返回 mSortedList.size();}}

现在唯一缺少的是实现过​​滤!


实现过滤逻辑

要实现过滤器逻辑,我们首先必须定义所有可能模型的List.对于这个例子,我从一个电影数组中创建了一个 ExampleModel 实例的 List:

private static final String[] MOVIES = new String[]{...};private static final ComparatorALPHABETICAL_COMPARATOR = 新比较器() {@覆盖公共 int 比较(ExampleModel a,ExampleModel b){返回 a.getText().compareTo(b.getText());}};私有 ExampleAdapter mAdapter;私有列表模型;私有 RecyclerView mRecyclerView;@覆盖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();对于(字符串电影:电影){mModels.add(new ExampleModel(movie));}mAdapter.add(mModels);}

这里没有什么特别的,我们只是实例化Adapter 并将其设置为RecyclerView.之后,我们从 MOVIES 数组中的电影名称创建一个 List 模型.然后我们将所有模型添加到SortedList.

现在我们可以回到我们之前定义的 onQueryTextChange() 并开始实现过滤器逻辑:

@Override公共布尔 onQueryTextChange(字符串查询){最终列表过滤模型列表 = 过滤器(mModels,查询);mAdapter.replaceAll(filteredModelList);mBinding.recyclerView.scrollToPosition(0);返回真;}

这又是很直接的了.我们调用filter()方法,传入ExampleModelList和查询字符串.然后我们在 Adapter 上调用 replaceAll() 并传入 filter() 返回的过滤后的 List.我们还必须在 RecyclerView 上调用 scrollToPosition(0) 以确保用户在搜索某些内容时始终可以看到所有项目.否则 RecyclerView 可能会在过滤时停留在向下滚动的位置并随后隐藏一些项目.滚动到顶部可确保在搜索时获得更好的用户体验.

现在唯一要做的就是实现 filter() 本身:

私有静态列表过滤器(列表<示例模型>模型,字符串查询){最终字符串lowerCaseQuery = query.toLowerCase();最终列表过滤模型列表 = 新的 ArrayList<>();对于(示例模型模型:模型){final String text = model.getText().toLowerCase();如果(text.contains(lowerCaseQuery)){过滤模型列表.添加(模型);}}返回已过滤的ModelList;}

我们在这里做的第一件事是在查询字符串上调用 toLowerCase().我们不希望我们的搜索函数区分大小写,通过对我们比较的所有字符串调用 toLowerCase(),我们可以确保无论大小写都返回相同的结果.然后它只是遍历我们传递给它的 List 中的所有模型,并检查查询字符串是否包含在模型的文本中.如果是,则将模型添加到过滤后的 List.

就是这样!以上代码将在 API 级别 7 及更高级别上运行,从 API 级别 11 开始,您可以免费获得项目动画!

我意识到这是一个非常详细的描述,这可能使整个事情看起来比实际更复杂,但是有一种方法可以概括整个问题并实现基于 AdapterSortedList 上更简单.


概括问题并简化适配器

在本节中,我不会详细介绍 - 部分原因是我在 Stack Overflow 上遇到了答案的字符限制,也因为上面已经解释了大部分内容 - 但总结一下变化:我们可以实施一个基本的 Adapter 类,它已经负责处理 SortedList 以及将模型绑定到 ViewHolder 实例,并提供了一种方便的方法来实现Adapter 基于 SortedList.为此,我们必须做两件事:

  • 我们需要创建一个所有模型类都必须实现的ViewModel接口
  • 我们需要创建一个 ViewHolder 子类,它定义了一个 bind() 方法,Adapter 可以用来自动绑定模型.

这使我们可以只关注应该在 RecyclerView 中显示的内容,只需实现模型和相应的 ViewHolder 实现.使用这个基类,我们不必担心 Adapter 及其 SortedList 的复杂细节.

SortedListAdapter

由于 StackOverflow 上答案的字符限制,我无法完成实现此基类的每一步,甚至无法在此处添加完整的源代码,但您可以找到此基类的完整源代码 - 我称之为SortedListAdapter - 在这个 GitHub Gist 中.

为了让您的生活更简单,我在 jCenter 上发布了一个包含 SortedListAdapter 的库!如果你想使用它,那么你需要做的就是将此依赖项添加到应用程序的 build.gradle 文件中:

编译'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'

您可以在在图书馆主页上找到有关该图书馆的更多信息.

使用 SortedListAdapter

要使用 SortedListAdapter,我们必须进行两个更改:

  • 更改ViewHolder,使其扩展SortedListAdapter.ViewHolder.类型参数应该是应该绑定到此 ViewHolder 的模型 - 在本例中为 ExampleModel.您必须在 performBind() 而不是 bind() 中将数据绑定到您的模型.

     公共类 ExampleViewHolder 扩展 SortedListAdapter.ViewHolder{私有最终 ItemExampleBinding mBinding;公共 ExampleViewHolder(ItemExampleBinding 绑定) {超级(绑定.getRoot());mBinding = 绑定;}@覆盖protected void performBind(ExampleModel item) {mBinding.setModel(item);}}

  • 确保您的所有模型都实现了 ViewModel 接口:

     公共类 ExampleModel 实现了 SortedListAdapter.ViewModel {...}

之后,我们只需更新 ExampleAdapter 以扩展 SortedListAdapter 并删除我们不再需要的所有内容.类型参数应该是您正在使用的模型类型 - 在本例中为 ExampleModel.但如果您使用不同类型的模型,则将类型参数设置为 ViewModel.

public class ExampleAdapter extends SortedListAdapter{公共示例适配器(上下文上下文,比较器<示例模型>比较器){超级(上下文,ExampleModel.class,比较器);}@覆盖受保护的 ViewHolderonCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) {最终 ItemExampleBinding 绑定 = ItemExampleBinding.inflate(inflater, parent, false);返回新的 ExampleViewHolder(binding);}@覆盖protected boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {返回 item1.getId() == item2.getId();}@覆盖protected boolean areItemContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {返回 oldItem.equals(newItem);}}

之后我们就完成了!然而最后要提到的是:SortedListAdapter 没有相同的 add()remove()replaceAll() 方法我们原来的 ExampleAdapter 有.它使用一个单独的 Editor 对象来修改列表中的项目,这些项目可以通过 edit() 方法访问.因此,如果您想删除或添加项目,您必须调用 edit() 然后添加和删除此 Editor 实例上的项目,完成后,调用 commit() 以将更改应用于 SortedList:

mAdapter.edit().remove(modelToRemove).add(listOfModelsToAdd).犯罪();

您以这种方式进行的所有更改都会一起批处理以提高性能.我们在上面章节中实现的 replaceAll() 方法也出现在这个 Editor 对象上:

mAdapter.edit().replaceAll(mModels).犯罪();

如果您忘记调用 commit(),那么您的任何更改都不会应用!

I am trying to implement the SearchView from the support library. I want the user to be to use the SearchView to filter a List of movies in a RecyclerView.

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. I have seen a few examples but none of them show results as you start typing.

This is my 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:

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);
        }
    }
}

解决方案

Introduction

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.

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:

Anyway lets get started.


Setting up the SearchView

In the folder res/menu create a new file called main_menu.xml. In it add an item and set the actionViewClass to 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. Your xml file should look something like this:

<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:

@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. We will implement the filter logic later on in onQueryTextChange() once we are finished implementing the Adapter.


Setting up the 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. 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:

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.

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.

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. It takes care of notifying the Adapter about changes to the data set and does so it a very efficient way. 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. But instead of sorting a List it is used to sort the items in the RecyclerView!

The SortedList interacts with the Adapter through a Callback class which you have to implement:

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. 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.

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. In this example I am just passing the call to a Comparator which compares the two models. 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. If you model classes have a correct equals() and hashCode() implementation you can usually just implement it like above. If we add an equals() and hashCode() implementation to the ExampleModel class it should look something like this:

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! So you don't have to implement them yourself. Look up on the internet how it works in your 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. 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. If your models have an id you would usually compare just the id in this method. 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.

With the SortedList.Callback correctly implemented we can create an instance of the 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. The other parameter is just the SortedList.Callback we defined above.

Now let's get down to business: If we implement the Adapter with a SortedList it 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.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.

Now we are almost done! But we first need a way to add or remove items to the Adapter. For this purpose we can add methods to the Adapter which allow us to add and remove items to the 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! Aside from that the implementation of these methods is pretty straight forward with one exception: the remove method which removes a List of models. 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. Calling beginBatchedUpdates() at the beginning batches all the changes we are going to make to the SortedList together and improves performance. When we call endBatchedUpdates() the RecyclerView is notified about all the changes at once.

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. 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.

Anyway, what I usually prefer is one method which allows me to replace all items in the RecyclerView at once. Remove everything which is not in the List and add all items which are missing from the 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.

And with that the Adapter is complete. 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!


Implementing the filter logic

To implement the filter logic we first have to define a List of all possible models. For this example I create a List of ExampleModel instances from an array of movies:

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. After that we create a List of models from the movie names in the MOVIES array. Then we add all the models to the SortedList.

Now we can go back to onQueryTextChange() which we defined earlier and start implementing the filter logic:

@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 ExampleModels as well as the query string. We then call replaceAll() on the Adapter and pass in the filtered List returned by filter(). We also have to call scrollToPosition(0) on the RecyclerView to ensure that the user can always see all items when searching for something. Otherwise the RecyclerView might stay in a scrolled down position while filtering and subsequently hide a few items. Scrolling to the top ensures a better user experience while searching.

The only thing left to do now is to implement filter() itself:

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. 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. 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. If it is then the model is added to the filtered 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!

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.


Generalizing the problem and simplifying the 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. For that we have to do two things:

  • We need to create a ViewModel interface which all model classes have to implement
  • We need to create a ViewHolder subclass which defines a bind() method the Adapter can use to bind models automatically.

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. Using this base class we don't have to worry about the intricate details of the Adapter and its SortedList.

SortedListAdapter

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.

To make your life simple I have published a library on jCenter which contains the SortedListAdapter! If you want to use it then all you need to do is add this dependency to your app's build.gradle file:

compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'

You can find more information about this library on the library homepage.

Using the SortedListAdapter

To use the SortedListAdapter we have to make two changes:

  • Change the ViewHolder so that it extends SortedListAdapter.ViewHolder. The type parameter should be the model which should be bound to this ViewHolder - in this case ExampleModel. You have to bind data to your models in performBind() instead of 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:

     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. The type parameter should be the type of model you are working with - in this case ExampleModel. But if you are working with different types of models then set the type parameter to 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. It uses a separate Editor object to modify the items in the list which can be accessed through the edit() method. 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:

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:

mAdapter.edit()
        .replaceAll(mModels)
        .commit();

If you forget to call commit() then none of your changes will be applied!

这篇关于如何使用 SearchView 过滤 RecyclerView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆