如何过滤一个RecyclerView与一个SearchView

我正在尝试从支持库实现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);
}
}
}
286083 次浏览

简介

因为从你的问题中不清楚你到底遇到了什么麻烦,我写了这个关于如何实现这个功能的快速演练;如果你还有问题,请提出来。

我在这个< >强GitHub库< / >强中有一个工作示例来说明我在这里谈论的所有内容。

在任何情况下,结果应该是这样的:

demo image

如果你想先玩一下演示应用,你可以从play Store安装它:

Get it on谷歌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>

在你的FragmentActivity中,你必须像往常一样膨胀这个菜单xml,然后你可以寻找包含SearchViewMenuItem,并实现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已经可以使用了。一旦我们完成了Adapter的实现,我们将在后面的onQueryTextChange()中实现过滤器逻辑。


设置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>

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

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

同样没有什么特别的。它只是使用数据绑定将模型类绑定到这个布局,就像我们在上面的布局xml中定义的那样。

现在我们终于可以进入真正有趣的部分:编写适配器。我将跳过Adapter的基本实现,而是将注意力集中在与这个答案相关的部分。

但首先有一件事我们必须讨论:< >强SortedList < / >强类。


SortedList

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

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

在回调函数顶部的方法中,如onMovedonInserted等,你必须调用与你的Adapter等效的notify方法。底部的三个方法compareareContentsTheSameareItemsTheSame你必须根据你想要显示的对象类型和这些对象应该以什么顺序出现在屏幕上来实现。

让我们来逐个介绍这些方法:

@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}

这就是我前面谈到的compare()方法。在这个例子中,我只是将调用传递给一个Comparator来比较两个模型。如果您想让项目按字母顺序显示在屏幕上。这个比较器看起来是这样的:

private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};

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

@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}

此方法的目的是确定模型的内容是否已更改。SortedList使用它来确定是否需要调用更改事件——换句话说,RecyclerView是否应该交叉褪色旧版本和新版本。如果你建模类有正确的equals()hashCode()实现,你通常可以像上面那样实现它。如果我们将equals()hashCode()实现添加到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()实现,只需按下按钮!所以你不需要自己实现它们。在互联网上查找它是如何在您的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

现在让我们进入正题:如果我们用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();
}
}

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

现在我们差不多完成了!但我们首先需要一种向Adapter中添加或删除项的方法。为此,我们可以在Adapter中添加方法,这些方法允许我们向SortedList中添加和删除项:

public void add(ExampleModel model) {
mSortedList.add(model);
}


public void remove(ExampleModel model) {
mSortedList.remove(model);
}


public void add(List<ExampleModel> models) {
mSortedList.addAll(models);
}


public void remove(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (ExampleModel model : models) {
mSortedList.remove(model);
}
mSortedList.endBatchedUpdates();
}

这里我们不需要调用任何通知方法,因为SortedList已经通过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。在这个例子中,我从一个电影数组中创建了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);
}

这里没有什么特别的事情,我们只是实例化了Adapter并将其设置为RecyclerView。之后,我们根据MOVIES数组中的电影名称创建一个List的模型。然后我们将所有模型添加到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(),并传入ExampleModels的List以及查询字符串。然后在Adapter上调用replaceAll(),并传入由filter()返回的过滤后的List。我们还必须在RecyclerView上调用scrollToPosition(0),以确保用户在搜索某项时总是能看到所有项。否则,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开始,你可以免费获得项目动画!

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


泛化问题并简化适配器

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

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

这允许我们只通过实现模型和相应的ViewHolder实现来专注于应该在RecyclerView中显示的内容。使用这个基类,我们不必担心Adapter及其SortedList的复杂细节。

SortedListAdapter

由于StackOverflow上的答案的字符限制,我不能通过实现这个基类的每一步,甚至在这里添加完整的源代码,但你可以在这个< >强GitHub依据< / >强中找到这个基类的完整源代码-我称之为SortedListAdapter

为了让你的生活变得简单,我在jCenter上发布了一个库,其中包含SortedListAdapter!如果你想使用它,那么你所需要做的就是将这个依赖添加到应用程序的构建中。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并删除我们不再需要的所有东西。类型参数应该是你正在使用的模型的类型——在本例中是ExampleModel。但是如果你使用不同类型的模型,那么将类型参数设置为ViewModel

public class ExampleAdapter extends SortedListAdapter<ExampleModel> {


public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
super(context, ExampleModel.class, comparator);
}


@Override
protected ViewHolder<? extends ExampleModel> onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
return new ExampleViewHolder(binding);
}


@Override
protected boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}


@Override
protected boolean areItemContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
}

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

mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();

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

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

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

我已经解决了同样的问题使用链接与一些修改。用卡片在RecyclerView上搜索过滤器。这可能吗?(希望这有帮助)。

这是我的适配器类

public class ContactListRecyclerAdapter extends RecyclerView.Adapter<ContactListRecyclerAdapter.ContactViewHolder> implements Filterable {


Context mContext;
ArrayList<Contact> customerList;
ArrayList<Contact> parentCustomerList;




public ContactListRecyclerAdapter(Context context,ArrayList<Contact> customerList)
{
this.mContext=context;
this.customerList=customerList;
if(customerList!=null)
parentCustomerList=new ArrayList<>(customerList);
}


// other overrided methods


@Override
public Filter getFilter() {
return new FilterCustomerSearch(this,parentCustomerList);
}
}

/ /过滤类

import android.widget.Filter;
import java.util.ArrayList;




public class FilterCustomerSearch extends Filter
{
private final ContactListRecyclerAdapter mAdapter;
ArrayList<Contact> contactList;
ArrayList<Contact> filteredList;


public FilterCustomerSearch(ContactListRecyclerAdapter mAdapter,ArrayList<Contact> contactList) {
this.mAdapter = mAdapter;
this.contactList=contactList;
filteredList=new ArrayList<>();
}


@Override
protected FilterResults performFiltering(CharSequence constraint) {
filteredList.clear();
final FilterResults results = new FilterResults();


if (constraint.length() == 0) {
filteredList.addAll(contactList);
} else {
final String filterPattern = constraint.toString().toLowerCase().trim();


for (final Contact contact : contactList) {
if (contact.customerName.contains(constraint)) {
filteredList.add(contact);
}
else if (contact.emailId.contains(constraint))
{
filteredList.add(contact);


}
else if(contact.phoneNumber.contains(constraint))
filteredList.add(contact);
}
}
results.values = filteredList;
results.count = filteredList.size();
return results;
}


@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
mAdapter.customerList.clear();
mAdapter.customerList.addAll((ArrayList<Contact>) results.values);
mAdapter.notifyDataSetChanged();
}

/ / Activity类

public class HomeCrossFadeActivity extends AppCompatActivity implements View.OnClickListener,OnFragmentInteractionListener,OnTaskCompletedListner
{
Fragment fragment;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_homecrossfadeslidingpane2);CardView mCard;
setContentView(R.layout.your_main_xml);}
//other overrided methods
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.


MenuInflater inflater = getMenuInflater();
// Inflate menu to add items to action bar if it is present.
inflater.inflate(R.menu.menu_customer_view_and_search, menu);
// Associate searchable configuration with the SearchView
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView =
(SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setQueryHint("Search Customer");
searchView.setSearchableInfo(
searchManager.getSearchableInfo(getComponentName()));


searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}


@Override
public boolean onQueryTextChange(String newText) {
if(fragment instanceof CustomerDetailsViewWithModifyAndSearch)
((CustomerDetailsViewWithModifyAndSearch)fragment).adapter.getFilter().filter(newText);
return false;
}
});






return true;
}
}

在OnQueryTextChangeListener()方法中使用适配器。我已经把它铸造成碎片,因为我的适配器是碎片。如果它在您的活动类中,您可以直接使用适配器。

你所需要做的就是在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在适配器的构造函数中初始化,如itemsCopy.addAll(items)

如果这样做,只需从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;
}
});

这是一个通过姓名和电话号码过滤我的电话簿的例子。

遵循@Shruthi Kamoji的清洁方式,我们可以使用一个过滤器,它的意思是:

public abstract class GenericRecycleAdapter<E> extends RecyclerView.Adapter implements Filterable
{
protected List<E> list;
protected List<E> originalList;
protected Context context;


public GenericRecycleAdapter(Context context,
List<E> list)
{
this.originalList = list;
this.list = list;
this.context = context;
}


...


@Override
public Filter getFilter() {
return new Filter() {
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list = (List<E>) results.values;
notifyDataSetChanged();
}


@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<E> filteredResults = null;
if (constraint.length() == 0) {
filteredResults = originalList;
} else {
filteredResults = getFilteredResults(constraint.toString().toLowerCase());
}


FilterResults results = new FilterResults();
results.values = filteredResults;


return results;
}
};
}


protected List<E> getFilteredResults(String constraint) {
List<E> results = new ArrayList<>();


for (E item : originalList) {
if (item.getName().toLowerCase().contains(constraint)) {
results.add(item);
}
}
return results;
}
}

这里的E是一个泛型类型,你可以使用你的类扩展它:

public class customerAdapter extends GenericRecycleAdapter<CustomerModel>

或者只是将E更改为你想要的类型(例如<CustomerModel>)

然后从searchView(你可以放在menu.xml上的小部件):

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String text) {
return false;
}


@Override
public boolean onQueryTextChange(String text) {
yourAdapter.getFilter().filter(text);
return true;
}
});

我建议用以下2件事修改@Xaver Kapeller的解决方案,以避免在您清除搜索文本后(过滤器不再工作),因为适配器的列表后面的大小小于过滤器列表,并且发生了IndexOutOfBoundsException。所以代码需要修改如下

public void addItem(int position, ExampleModel model) {
if(position >= mModel.size()) {
mModel.add(model);
notifyItemInserted(mModel.size()-1);
} else {
mModels.add(position, model);
notifyItemInserted(position);
}
}

并在moveItem功能中进行修改

public void moveItem(int fromPosition, int toPosition) {
final ExampleModel model = mModels.remove(fromPosition);
if(toPosition >= mModels.size()) {
mModels.add(model);
notifyItemMoved(fromPosition, mModels.size()-1);
} else {
mModels.add(toPosition, model);
notifyItemMoved(fromPosition, toPosition);
}
}

希望能对你有所帮助!

简单地在适配器中创建两个列表,一个是original,一个是temp和实现了滤过性的

    @Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
final FilterResults oReturn = new FilterResults();
final ArrayList<T> results = new ArrayList<>();
if (origList == null)
origList = new ArrayList<>(itemList);
if (constraint != null && constraint.length() > 0) {
if (origList != null && origList.size() > 0) {
for (final T cd : origList) {
if (cd.getAttributeToSearch().toLowerCase()
.contains(constraint.toString().toLowerCase()))
results.add(cd);
}
}
oReturn.values = results;
oReturn.count = results.size();//newly Aded by ZA
} else {
oReturn.values = origList;
oReturn.count = origList.size();//newly added by ZA
}
return oReturn;
}


@SuppressWarnings("unchecked")
@Override
protected void publishResults(final CharSequence constraint,
FilterResults results) {
itemList = new ArrayList<>((ArrayList<T>) results.values);
// FIXME: 8/16/2017 implement Comparable with sort below
///Collections.sort(itemList);
notifyDataSetChanged();
}
};
}

在哪里

public GenericBaseAdapter(Context mContext, List<T> itemList) {
this.mContext = mContext;
this.itemList = itemList;
this.origList = itemList;
}

适配器:

public void setFilter(List<Channel> newList){
mChannels = new ArrayList<>();
mChannels.addAll(newList);
notifyDataSetChanged();
}

在活动:

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}


@Override
public boolean onQueryTextChange(String newText) {
newText = newText.toLowerCase();
ArrayList<Channel> newList = new ArrayList<>();
for (Channel channel: channels){
String channelName = channel.getmChannelName().toLowerCase();
if (channelName.contains(newText)){
newList.add(channel);
}
}
mAdapter.setFilter(newList);
return true;
}
});

使用Android架构组件,通过使用LiveData,这可以很容易地用任何类型的适配器实现。你只需要做以下步骤:

设置你的数据从房间 数据库返回为LiveData,如下例所示:

@Dao
public interface CustomDAO{


@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
public LiveData<List<Word>> searchFor(String searchquery);
}

2.创建一个视图模型对象,通过连接用户界面的方法实时更新数据

public class CustomViewModel extends AndroidViewModel {


private final AppDatabase mAppDatabase;


public WordListViewModel(@NonNull Application application) {
super(application);
this.mAppDatabase = AppDatabase.getInstance(application.getApplicationContext());
}


public LiveData<List<Word>> searchQuery(String query) {
return mAppDatabase.mWordDAO().searchFor(query);
}


}

3.调用你的数据从视图模型通过传递查询通过onQueryTextListener如下所示:

onCreateOptionsMenu内部设置监听器,如下所示

searchView.setOnQueryTextListener(onQueryTextListener);

在SearchActivity类的某处设置查询侦听器,如下所示

private android.support.v7.widget.SearchView.OnQueryTextListener onQueryTextListener =
new android.support.v7.widget.SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
getResults(query);
return true;
}


@Override
public boolean onQueryTextChange(String newText) {
getResults(newText);
return true;
}


private void getResults(String newText) {
String queryText = "%" + newText + "%";
mCustomViewModel.searchQuery(queryText).observe(
SearchResultsActivity.this, new Observer<List<Word>>() {
@Override
public void onChanged(@Nullable List<Word> words) {
if (words == null) return;
searchAdapter.submitList(words);
}
});
}
};

请注意:步骤(1.)和(2.)是标准的AAC视图模型实现,这里唯一真正的“魔法”发生在OnQueryTextListener中,它会随着查询文本的变化动态更新列表的结果。

如果你需要更多关于此事的说明,请尽管问。 我希望这有助于:).

这是我对扩展@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++;
}
}

基本上,它所做的就是查看一个完整的列表,并逐个向过滤后的列表添加/删除项。

Recyclerview with searchview and clicklistener

在适配器中添加接口。

public interface SelectedUser{


void selectedUser(UserModel userModel);


}
在mainactivity中实现接口并覆盖该方法。 @Override public void selectedUser(UserModel UserModel) {

    startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));






}

完整教程和源代码: Recyclerview与searchview和onclicklistener < / p >

我不知道为什么每个人都用两个相同的列表来解决这个问题。这使用太多内存…

为什么不只是隐藏未找到的元素,并简单地将它们的指数存储在Set中,以便以后能够恢复它们?这样内存就少多了,特别是当你的对象很大的时候。

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.SampleViewHolders>{
private List<MyObject> myObjectsList; //holds the items of type MyObject
private Set<Integer> foundObjects; //holds the indices of the found items


public MyRecyclerViewAdapter(Context context, List<MyObject> myObjectsList)
{
this.myObjectsList = myObjectsList;
this.foundObjects = new HashSet<>();
//first, add all indices to the indices set
for(int i = 0; i < this.myObjectsList.size(); i++)
{
this.foundObjects.add(i);
}
}


@NonNull
@Override
public SampleViewHolders onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.my_layout_for_staggered_grid, null);
MyRecyclerViewAdapter.SampleViewHolders rcv = new MyRecyclerViewAdapter.SampleViewHolders(layoutView);
return rcv;
}


@Override
public void onBindViewHolder(@NonNull SampleViewHolders holder, int position)
{
//look for object in O(1) in the indices set
if(!foundObjects.contains(position))
{
//object not found => hide it.
holder.hideLayout();
return;
}
else
{
//object found => show it.
holder.showLayout();
}


//holder.imgImageView.setImageResource(...)
//holder.nameTextView.setText(...)
}


@Override
public int getItemCount() {
return myObjectsList.size();
}


public void findObject(String text)
{
//look for "text" in the objects list
for(int i = 0; i < myObjectsList.size(); i++)
{
//if it's empty text, we want all objects, so just add it to the set.
if(text.length() == 0)
{
foundObjects.add(i);
}
else
{
//otherwise check if it meets your search criteria and add it or remove it accordingly
if (myObjectsList.get(i).getName().toLowerCase().contains(text.toLowerCase()))
{
foundObjects.add(i);
}
else
{
foundObjects.remove(i);
}
}
}
notifyDataSetChanged();
}


public class SampleViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener
{
public ImageView imgImageView;
public TextView nameTextView;


private final CardView layout;
private final CardView.LayoutParams hiddenLayoutParams;
private final CardView.LayoutParams shownLayoutParams;


        

public SampleViewHolders(View itemView)
{
super(itemView);
itemView.setOnClickListener(this);
imgImageView = (ImageView) itemView.findViewById(R.id.some_image_view);
nameTextView = (TextView) itemView.findViewById(R.id.display_name_textview);


layout = itemView.findViewById(R.id.card_view); //card_view is the id of my androidx.cardview.widget.CardView in my xml layout
//prepare hidden layout params with height = 0, and visible layout params for later - see hideLayout() and showLayout()
hiddenLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
hiddenLayoutParams.height = 0;
shownLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}


@Override
public void onClick(View view)
{
//implement...
}


private void hideLayout() {
//hide the layout
layout.setLayoutParams(hiddenLayoutParams);
}


private void showLayout() {
//show the layout
layout.setLayoutParams(shownLayoutParams);
}
}
}

我简单地用EditText作为我的搜索框:

cardsSearchTextView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {


}


@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {


}


@Override
public void afterTextChanged(Editable editable) {
myViewAdapter.findObject(editable.toString().toLowerCase());
}
});

结果:

Search example gif .

如果你想搜索按钮点击,然后这工作得很好。

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

在哪里< >强filterIcon < / >强是按钮,homeSearchEdit是editText(我们申请搜索的地方)。

Android已经提供了DiffUtil.Callback()DiffUtil.ItemCallback<T>,它们帮助我们很好地过滤回收视图

DiffUtil是一个计算两者之差的实用程序类 列出并输出转换第一个更新操作的列表

https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil

DiffUtil.Callback()与RecyclerView一起使用。适配器

而且

DiffUtil。ItemCallback与ListAdapter一起使用

使用RecyclerView过滤

创建你的RecyclerView,就像你通常会覆盖

onCreateViewHolder


onBindViewHolder


getItemCount

和扩展RecyclerView.ViewHolder Class

就像您所做的那样(这是代码片段的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
}
}

现在创建另一个类来实现DiffUtil.Callback()

这个类将帮助将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]


}

在你的活动或片段类设置你的适配器和过滤器

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)


}

使用ListAdapter进行筛选

我们将使用可过滤接口来帮助我们过滤(仍然在思考为什么我不应该直接使用过滤器函数来获取filteredLists和submitList(filteredLists))

创建你的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
}
}
}

现在创建另一个实现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
}
}

在MainActivity或Fragment中设置适配器和过滤器

private fun setupAdapter() {


adapter = CardAdapter(mItems)


recyclerView.adapter = adapter


}


fun filter(searchString : String){


adapter.filter.filter(searchString)


}