我正在尝试从支持库中实现 SearchView
。我希望用户使用 SearchView
过滤 List
中的电影 RecyclerView
。
到目前为止,我已经学习了一些教程,并且已将 SearchView
添加到 ActionBar
,但我不确定从这里去哪里。我已经看过一些示例,但是当您开始输入时,它们都没有显示结果。
这是我的 MainActivity
:
public class MainActivity extends ActionBarActivity {
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
RecyclerView.Adapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new CardAdapter() {
@Override
public Filter getFilter() {
return null;
}
};
mRecyclerView.setAdapter(mAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
这是我的 Adapter
:
public abstract class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> implements Filterable {
List<Movie> mItems;
public CardAdapter() {
super();
mItems = new ArrayList<Movie>();
Movie movie = new Movie();
movie.setName("Spiderman");
movie.setRating("92");
mItems.add(movie);
movie = new Movie();
movie.setName("Doom 3");
movie.setRating("91");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers");
movie.setRating("88");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 2");
movie.setRating("87");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 3");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Noah");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 2");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 3");
movie.setRating("86");
mItems.add(movie);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
Movie movie = mItems.get(i);
viewHolder.tvMovie.setText(movie.getName());
viewHolder.tvMovieRating.setText(movie.getRating());
}
@Override
public int getItemCount() {
return mItems.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
public TextView tvMovie;
public TextView tvMovieRating;
public ViewHolder(View itemView) {
super(itemView);
tvMovie = (TextView)itemView.findViewById(R.id.movieName);
tvMovieRating = (TextView)itemView.findViewById(R.id.movieRating);
}
}
}
原文由 Jacques Krause 发布,翻译遵循 CC BY-SA 4.0 许可协议
介绍
由于您的问题并不清楚您到底遇到了什么问题,因此我写了这个关于如何实现此功能的快速演练;如果您仍有疑问,请随时提出。
我在这个 GitHub Repository 中有一个关于我所说的一切的工作示例。
无论如何,结果应该是这样的:
如果您首先想使用演示应用程序,您可以从 Play 商店安装它:
无论如何,让我们开始吧。
设置
SearchView
在文件夹
res/menu
创建一个名为main_menu.xml
的新文件。在其中添加一个项目并将actionViewClass
设置为android.support.v7.widget.SearchView
。由于您使用的是支持库,因此您必须使用支持库的命名空间来设置actionViewClass
属性。您的 xml 文件应如下所示:In your
Fragment
orActivity
you have to inflate this menu xml like usual, then you can look for theMenuItem
which contains theSearchView
并实现OnQueryTextListener
我们将使用它来监听输入到SearchView
的文本的变化:现在
SearchView
可以使用了。一旦我们完成了Adapter
的实现,我们稍后将在onQueryTextChange()
中实现过滤器逻辑。设置
Adapter
首先,这是我将用于此示例的模型类:
这只是您的基本模型,它将在
RecyclerView
中显示文本。这是我将用来显示文本的布局:如您所见,我使用数据绑定。如果您以前从未使用过数据绑定,请不要气馁!它非常简单和强大,但是我无法解释它在这个答案的范围内是如何工作的。
这是
ViewHolder
用于ExampleModel
类:再次没什么特别的。它只是使用数据绑定将模型类绑定到这个布局,正如我们在上面的布局 xml 中定义的那样。
现在我们终于可以来到真正有趣的部分:编写适配器。我将跳过
Adapter
的基本实现,而是专注于与此答案相关的部分。但首先我们要谈一件事:
SortedList
类。排序列表
SortedList
是一个非常棒的工具,它是RecyclerView
库的一部分。它负责通知Adapter
关于数据集的更改,这是一种非常有效的方式。它唯一需要您做的就是指定元素的顺序。您需要通过实现compare()
方法来比较 --- 中的两个元素,就像Comparator
SortedList
。但不是对List
进行排序,而是用于对RecyclerView
中的项目进行排序!Adapter
通过Callback
您必须实现的类与SortedList
交互:在回调顶部的方法中,例如
onMoved
、onInserted
等,您必须调用Adapter
的等效通知方法。底部的三个方法compare
,areContentsTheSame
和areItemsTheSame
你必须根据你想要显示什么样的对象以及这些对象应该以什么顺序来实现出现在屏幕上。让我们一一介绍这些方法:
这就是我前面讲的
compare()
方法。在此示例中,我只是将调用传递给Comparator
比较两个模型。如果您希望项目按字母顺序显示在屏幕上。此比较器可能如下所示:现在让我们看看下一个方法:
此方法的目的是确定模型的内容是否已更改。
SortedList
使用它来确定是否需要调用更改事件 - 换句话说,RecyclerView
是否应该交叉淡入淡出旧版本和新版本。如果您的模型类具有正确的equals()
和hashCode()
实现,您通常可以像上面那样实现它。如果我们将equals()
和hashCode()
实现添加到ExampleModel
类,它应该看起来像这样:快速旁注:大多数 IDE,如 Android Studio、IntelliJ 和 Eclipse 都具有生成
equals()
和hashCode()
实现的功能,只需按一下按钮!所以你不必自己实现它们。在互联网上查找它在您的 IDE 中的工作原理!现在让我们看一下最后一种方法:
SortedList
使用这种方法来检查两个项目是否指的是同一个东西。简单来说(不解释SortedList
是如何工作的),这用于确定对象是否已包含在List
中,以及是否需要添加、移动或更改动画玩过。如果您的模型有 id,您通常会在此方法中只比较 id。如果他们不这样做,您需要找出其他方法来检查这一点,但是您最终实现这取决于您的特定应用程序。通常,给所有模型一个 id 是最简单的选择——例如,如果您从数据库中查询数据,它可能是主键字段。正确实施
SortedList.Callback
我们可以创建一个SortedList
的实例:作为
SortedList
的构造函数中的第一个参数,您需要传递模型的类。另一个参数就是我们上面定义的SortedList.Callback
。现在让我们开始做正事:如果我们用
Adapter
实现SortedList
它应该看起来像这样:用于对项目进行排序的
Comparator
是通过构造函数传入的,因此我们可以使用相同的Adapter
即使项目应该以不同的顺序显示。现在我们几乎完成了!但是我们首先需要一种向
Adapter
添加或删除项目的方法。为此,我们可以向Adapter
添加方法,这允许我们向SortedList
添加和删除项目:我们不需要在这里调用任何通知方法,因为
SortedList
已经通过SortedList.Callback
执行此操作!除此之外,这些方法的实现非常简单,但有一个例外:remove 方法删除了List
模型。由于SortedList
只有一个可以删除单个对象的删除方法,我们需要遍历列表并逐个删除模型。在开始时调用beginBatchedUpdates()
将我们将对SortedList
所做的所有更改一起批处理并提高性能。当我们调用endBatchedUpdates()
RecyclerView
会立即通知所有更改。此外,您必须了解的是,如果您将对象添加到
SortedList
并且它已经在SortedList
中,则不会再次添加。相反,SortedList
使用areContentsTheSame()
方法来确定对象是否已更改 - 并且如果它在RecyclerView
中包含该项目,将被更新。无论如何,我通常更喜欢一种方法,它允许我一次替换
RecyclerView
中的所有项目。删除List
SortedList
缺少的所有项目:此方法再次将所有更新批处理在一起以提高性能。第一个循环是相反的,因为在开始时删除一个项目会弄乱它之后出现的所有项目的索引,这在某些情况下会导致数据不一致等问题。之后,我们只需将
List
添加到SortedList
使用addAll()
添加所有尚未在SortedList
中的项目就像我上面描述的 - 更新已经在SortedList
但已经改变的所有项目。这样
Adapter
就完成了。整个事情应该是这样的:现在唯一缺少的是实现过滤!
实现过滤器逻辑
要实现过滤器逻辑,我们首先必须定义所有可能模型的
List
。对于这个例子,我从一系列电影中创建了一个List
的ExampleModel
实例:这里没什么特别的,我们只是实例化
Adapter
并将其设置为RecyclerView
。之后,我们根据MOVIES
数组中的电影名称创建一个List
模型。然后我们将所有模型添加到SortedList
。现在我们可以回到我们之前定义的
onQueryTextChange()
并开始实现过滤器逻辑:这又很简单。我们调用方法
filter()
并传入List
的ExampleModel
以及查询字符串。然后我们在replaceAll()
Adapter
并传入过滤后的List
由filter()
返回。我们还必须在scrollToPosition(0)
RecyclerView
以确保用户在搜索内容时始终可以看到所有项目。否则RecyclerView
可能会在过滤时停留在向下滚动的位置并随后隐藏一些项目。滚动到顶部可确保在搜索时获得更好的用户体验。现在唯一要做的就是实现
filter()
本身:我们在这里做的第一件事是在查询字符串上调用
toLowerCase()
。我们不希望我们的搜索函数区分大小写,通过在我们比较的所有字符串上调用toLowerCase()
,我们可以确保无论大小写都返回相同的结果。然后它只是遍历我们传入它的List
中的所有模型,并检查查询字符串是否包含在模型的文本中。如果是,则将模型添加到过滤后的List
。就是这样!上面的代码将在 API 级别 7 及更高版本上运行,从 API 级别 11 开始,您可以免费获得项目动画!
我意识到这是一个非常详细的描述,这可能会使整个事情看起来比实际更复杂,但是有一种方法可以概括整个问题并基于一个
SortedList
实现Adapter
---
简单得多。泛化问题并简化适配器
在本节中,我不会详细介绍 - 部分原因是我在 Stack Overflow 上遇到了字符数限制,但也因为上面已经解释了大部分内容 - 但总结一下变化:我们可以实现一个基础
Adapter
class which already takes care of dealing with theSortedList
as well as binding models toViewHolder
instances and provides a convenient way to implement anAdapter
基于SortedList
。为此,我们必须做两件事:ViewModel
所有模型类都必须实现的接口ViewHolder
子类,它定义了一个bind()
方法Adapter
可以用来自动绑定模型。这使我们可以只关注应该在
RecyclerView
中显示的内容,只需实现模型和相应的ViewHolder
实现。使用这个基类,我们不必担心Adapter
及其SortedList
的复杂细节。排序列表适配器
由于 StackOverflow 上答案的字符限制,我无法完成实现此基类的每一步,甚至无法在此处添加完整的源代码,但您可以找到此基类的完整源代码——我称之为
SortedListAdapter
在这个 GitHub Gist 中。为了让您的生活更简单,我在 jCenter 上发布了一个库,其中包含
SortedListAdapter
!如果您想使用它,那么您需要做的就是将此依赖项添加到您的应用程序的 build.gradle 文件中:您可以 在图书馆主页上 找到有关该图书馆的更多信息。
使用 SortedListAdapter
要使用
SortedListAdapter
我们必须进行两项更改:ViewHolder
使其扩展SortedListAdapter.ViewHolder
。类型参数应该是应该绑定到这个的模型ViewHolder
- 在这种情况下ExampleModel
。您必须在performBind()
而不是bind()
中将数据绑定到您的模型。ViewModel
接口:之后,我们只需更新
ExampleAdapter
以扩展SortedListAdapter
并删除我们不再需要的所有内容。 type 参数应该是您正在使用的模型类型 - 在本例中为ExampleModel
。但是,如果您使用不同类型的模型,请将 type 参数设置为ViewModel
。之后我们就完成了! However one last thing to mention: The
SortedListAdapter
does not have the sameadd()
,remove()
orreplaceAll()
methods our originalExampleAdapter
有。它使用一个单独的Editor
对象来修改可以通过edit()
方法访问的列表中的项目。因此,如果您想删除或添加项目,您必须调用edit()
然后添加和删除此Editor
实例上的项目,完成后,调用commit()
在其上将更改应用于SortedList
:您以这种方式进行的所有更改都被批量处理以提高性能。我们在上面章节中实现的
replaceAll()
方法也出现在这个Editor
对象上:如果您忘记拨打
commit()
那么您的任何更改都不会应用!