简体   繁体   English

如何使用 SearchView 过滤 RecyclerView

[英]How to filter a RecyclerView with a SearchView

I am trying to implement the SearchView from the support library.我正在尝试从支持库中实现SearchView I want the user to be to use the SearchView to filter a List of movies in a RecyclerView .我希望用户使用SearchView过滤RecyclerView中的电影List

I have followed a few tutorials so far and I have added the SearchView to the ActionBar , but I am not really sure where to go from here.到目前为止,我已经学习了一些教程,并将SearchView添加到了ActionBar ,但我不确定从这里去哪里。 I have seen a few examples but none of them show results as you start typing.我已经看过一些示例,但是当您开始输入时,它们都没有显示结果。

This is my MainActivity :这是我的MainActivity

public class MainActivity extends ActionBarActivity {

    RecyclerView mRecyclerView;
    RecyclerView.LayoutManager mLayoutManager;
    RecyclerView.Adapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setHasFixedSize(true);

        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);

        mAdapter = new CardAdapter() {
            @Override
            public Filter getFilter() {
                return null;
            }
        };
        mRecyclerView.setAdapter(mAdapter);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

And this is my Adapter :这是我的Adapter

public abstract class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> implements Filterable {

    List<Movie> mItems;

    public CardAdapter() {
        super();
        mItems = new ArrayList<Movie>();
        Movie movie = new Movie();
        movie.setName("Spiderman");
        movie.setRating("92");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Doom 3");
        movie.setRating("91");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers");
        movie.setRating("88");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers 2");
        movie.setRating("87");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers 3");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Noah");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman 2");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman 3");
        movie.setRating("86");
        mItems.add(movie);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        Movie movie = mItems.get(i);
        viewHolder.tvMovie.setText(movie.getName());
        viewHolder.tvMovieRating.setText(movie.getRating());
    }

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

    class ViewHolder extends RecyclerView.ViewHolder{

        public TextView tvMovie;
        public TextView tvMovieRating;

        public ViewHolder(View itemView) {
            super(itemView);
            tvMovie = (TextView)itemView.findViewById(R.id.movieName);
            tvMovieRating = (TextView)itemView.findViewById(R.id.movieRating);
        }
    }
}

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 .我在这个GitHub Repository 中有一个关于我在这里谈论的所有内容的工作示例。

In any case the result should looks something like this:在任何情况下,结果都应该是这样的:

演示图片

If you first want to play around with the demo app you can install it from the Play Store:如果您首先想使用演示应用程序,您可以从 Play 商店安装它:

在 Google Play 上获取

Anyway lets get started.无论如何,让我们开始吧。


Setting up the SearchView设置搜索SearchView

In the folder res/menu create a new file called main_menu.xml .在文件夹res/menu创建一个名为main_menu.xml的新文件。 In it add an item and set the actionViewClass to android.support.v7.widget.SearchView .在其中添加一个项目并将actionViewClass设置为android.support.v7.widget.SearchView Since you are using the support library you have to use the namespace of the support library to set the actionViewClass attribute.由于您使用的是支持库,因此您必须使用支持库的命名空间来设置actionViewClass属性。 Your xml file should look something like this:您的 xml 文件应如下所示:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/action_search"
          android:title="@string/action_search"
          app:actionViewClass="android.support.v7.widget.SearchView"
          app:showAsAction="always"/>
      
</menu>

In your Fragment or Activity you have to inflate this menu xml like usual, then you can look for the MenuItem which contains the SearchView and implement the OnQueryTextListener which we are going to use to listen for changes to the text entered into the SearchView :在您的FragmentActivity您必须像往常一样OnQueryTextListener此菜单 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;
}

And now the SearchView is ready to be used.现在SearchView可以使用了。 We will implement the filter logic later on in onQueryTextChange() once we are finished implementing the Adapter .一旦我们完成了Adapter实现,我们稍后将在onQueryTextChange()实现过滤器逻辑。


Setting up the Adapter设置Adapter

First and foremost this is the model class I am going to use for this example:首先,这是我将用于此示例的模型类:

public class ExampleModel {

    private final long mId;
    private final String mText;

    public ExampleModel(long id, String text) {
        mId = id;
        mText = text;
    }

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }
}

It's just your basic model which will display a text in the RecyclerView .这只是您的基本模型,它将在RecyclerView显示文本。 This is the layout I am going to use to display the text:这是我将用于显示文本的布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="model"
            type="com.github.wrdlbrnft.searchablerecyclerviewdemo.ui.models.ExampleModel"/>

    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackground"
        android:clickable="true">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{model.text}"/>

    </FrameLayout>

</layout>

As you can see I use Data Binding.如您所见,我使用数据绑定。 If you have never worked with data binding before don't be discouraged!如果您以前从未使用过数据绑定,请不要气馁! It's very simple and powerful, however I can't explain how it works in the scope of this answer.它非常简单和强大,但是我无法解释它在这个答案的范围内是如何工作的。

This is the ViewHolder for the ExampleModel class:这是ExampleModel类的ViewHolder

public class ExampleViewHolder extends RecyclerView.ViewHolder {

    private final ItemExampleBinding mBinding;

    public ExampleViewHolder(ItemExampleBinding binding) {
        super(binding.getRoot());
        mBinding = binding;
    }

    public void bind(ExampleModel item) {
        mBinding.setModel(item);
    }
}

Again nothing special.再次没什么特别的。 It just uses data binding to bind the model class to this layout as we have defined in the layout xml above.它只是使用数据绑定将模型类绑定到这个布局,正如我们在上面的布局 xml 中定义的那样。

Now we can finally come to the really interesting part: Writing the Adapter.现在我们终于可以来到真正有趣的部分:编写适配器。 I am going to skip over the basic implementation of the Adapter and am instead going to concentrate on the parts which are relevant for this answer.我将跳过Adapter的基本实现,而是将注意力集中在与此答案相关的部分。

But first there is one thing we have to talk about: The SortedList class.但首先我们必须谈论一件事: SortedList类。


SortedList排序列表

The SortedList is a completely amazing tool which is part of the RecyclerView library. SortedList是一个非常了不起的工具,它是RecyclerView库的一部分。 It takes care of notifying the Adapter about changes to the data set and does so it a very efficient way.它负责通知Adapter有关数据集的更改,这是一种非常有效的方式。 The only thing it requires you to do is specify an order of the elements.它唯一需要您做的就是指定元素的顺序。 You need to do that by implementing a compare() method which compares two elements in the SortedList just like a Comparator .您需要通过实现compare()方法来做到这一点,该方法像Comparator一样Comparator SortedList两个元素。 But instead of sorting a List it is used to sort the items in the RecyclerView !但是不是对List进行排序,而是用于对RecyclerView的项目进行排序!

The SortedList interacts with the Adapter through a Callback class which you have to implement: SortedList通过您必须实现的Callback类与Adapter交互:

private final SortedList.Callback<ExampleModel> mCallback = new SortedList.Callback<ExampleModel>() {

    @Override
    public void onInserted(int position, int count) {
         mAdapter.notifyItemRangeInserted(position, count);
    }

    @Override
    public void onRemoved(int position, int count) {
        mAdapter.notifyItemRangeRemoved(position, count);
    }

    @Override
    public void onMoved(int fromPosition, int toPosition) {
        mAdapter.notifyItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onChanged(int position, int count) {
        mAdapter.notifyItemRangeChanged(position, count);
    }

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

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

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

In the methods at the top of the callback like onMoved , onInserted , etc. you have to call the equivalent notify method of your Adapter .在回调顶部的方法中,如onMovedonInserted等,您必须调用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.底部的三个方法compareareContentsTheSameareItemsTheSame您必须根据要显示的对象类型以及这些对象在屏幕上出现的顺序来实现。

Let's go through these methods one by one:让我们一一介绍这些方法:

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

This is the compare() method I talked about earlier.这就是我之前谈到的compare()方法。 In this example I am just passing the call to a Comparator which compares the two models.在这个例子中,我只是将调用传递给Comparator两个模型的Comparator If you want the items to appear in alphabetical order on the screen.如果您希望项目按字母顺序出现在屏幕上。 This comparator might look like this:这个比较器可能如下所示:

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

Now let's take a look at the next method:现在让我们看看下一个方法:

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

The purpose of this method is to determine if the content of a model has changed.此方法的目的是确定模型的内容是否已更改。 The SortedList uses this to determine if a change event needs to be invoked - in other words if the RecyclerView should crossfade the old and new version. SortedList使用它来确定是否需要调用更改事件 - 换句话说, RecyclerView是否应该对旧版本和新版本进行淡入淡出。 If you model classes have a correct equals() and hashCode() implementation you can usually just implement it like above.如果您的模型类具有正确的equals()hashCode()实现,您通常可以像上面那样实现它。 If we add an equals() and hashCode() implementation to the ExampleModel class it should look something like this:如果我们向ExampleModel类添加一个equals()hashCode()实现,它应该看起来像这样:

public class ExampleModel implements SortedListAdapter.ViewModel {

    private final long mId;
    private final String mText;

    public ExampleModel(long id, String text) {
        mId = id;
        mText = text;
    }

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ExampleModel model = (ExampleModel) o;

        if (mId != model.mId) return false;
        return mText != null ? mText.equals(model.mText) : model.mText == null;

    }

    @Override
    public int hashCode() {
        int result = (int) (mId ^ (mId >>> 32));
        result = 31 * result + (mText != null ? mText.hashCode() : 0);
        return result;
    }
}

Quick side note: Most IDE's like Android Studio, IntelliJ and Eclipse have functionality to generate equals() and hashCode() implementations for you at the press of a button!快速旁注:大多数 IDE 像 Android Studio、IntelliJ 和 Eclipse 都有功能,只需按一下按钮即可为您生成equals()hashCode()实现! So you don't have to implement them yourself.所以你不必自己实现它们。 Look up on the internet how it works in your IDE!在 Internet 上查找它在您的 IDE 中的工作方式!

Now let's take a look at the last method:现在让我们看看最后一个方法:

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

The SortedList uses this method to check if two items refer to the same thing. SortedList使用此方法来检查两个项目是否引用同一事物。 In simplest terms (without explaining how the SortedList works) this is used to determine if an object is already contained in the List and if either an add, move or change animation needs to be played.用最简单的术语(不解释SortedList工作原理),这用于确定对象是否已包含在List以及是否需要播放添加、移动或更改动画。 If your models have an id you would usually compare just the id in this method.如果你的模型有一个 id,你通常会在这个方法中只比较 id。 If they don't you need to figure out some other way to check this, but however you end up implementing this depends on your specific app.如果他们不需要,您需要找出其他方法来检查这一点,但最终实现这取决于您的特定应用程序。 Usually it is the simplest option to give all models an id - that could for example be the primary key field if you are querying the data from a database.通常,给所有模型一个 id 是最简单的选择 - 例如,如果您从数据库中查询数据,它可能是主键字段。

With the SortedList.Callback correctly implemented we can create an instance of the SortedList :正确实现SortedList.Callback ,我们可以创建SortedList的实例:

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

As the first parameter in the constructor of the SortedList you need to pass the class of your models.作为SortedList构造函数中的第一个参数,您需要传递模型的类。 The other parameter is just the SortedList.Callback we defined above.另一个参数就是我们上面定义的SortedList.Callback

Now let's get down to business: If we implement the Adapter with a SortedList it should look something like this:现在让我们进入正SortedList :如果我们使用SortedList实现Adapter ,它应该看起来像这样:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

    private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
        @Override
        public int compare(ExampleModel a, ExampleModel b) {
            return mComparator.compare(a, b);
        }

        @Override
        public void onInserted(int position, int count) {
            notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count) {
            notifyItemRangeChanged(position, count);
        }

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

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

    private final LayoutInflater mInflater;
    private final Comparator<ExampleModel> mComparator;

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
        mInflater = LayoutInflater.from(context);
        mComparator = comparator;
    }

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
        return new ExampleViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) {
        final ExampleModel model = mSortedList.get(position);
        holder.bind(model);
    }

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

The Comparator used to sort the item is passed in through the constructor so we can use the same Adapter even if the items are supposed to be displayed in a different order.用于对项目进行排序的Comparator是通过构造函数传入的,因此即使项目应该以不同的顺序显示,我们也可以使用相同的Adapter

Now we are almost done!现在我们快完成了! But we first need a way to add or remove items to the Adapter .但是我们首先需要一种向Adapter添加或删除项目的方法。 For this purpose we can add methods to the Adapter which allow us to add and remove items to the SortedList :为此,我们可以向Adapter添加方法,允许我们向SortedList添加和删​​除项目:

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

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

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

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

We don't need to call any notify methods here because the SortedList already does this for through the SortedList.Callback !我们不需要在这里调用任何通知方法,因为SortedList已经通过SortedList.Callback做到了这一点! Aside from that the implementation of these methods is pretty straight forward with one exception: the remove method which removes a List of models.除此之外,这些方法的实现非常简单,只有一个例外:删除模型List的 remove 方法。 Since the SortedList has only one remove method which can remove a single object we need to loop over the list and remove the models one by one.由于SortedList只有一个可以删除单个对象的 remove 方法,我们需要遍历列表并一个一个删除模型。 Calling beginBatchedUpdates() at the beginning batches all the changes we are going to make to the SortedList together and improves performance.在开始时调用beginBatchedUpdates()将我们将要对SortedList进行的所有更改批量化并提高性能。 When we call endBatchedUpdates() the RecyclerView is notified about all the changes at once.当我们调用endBatchedUpdates() ,会立即通知RecyclerView所有更改。

Additionally what you have to understand is that if you add an object to the SortedList and it is already in the SortedList it won't be added again.此外,您必须了解的是,如果您将一个对象添加到SortedList并且它已经在SortedList ,则不会再次添加它。 Instead the SortedList uses the areContentsTheSame() method to figure out if the object has changed - and if it has the item in the RecyclerView will be updated.相反, SortedList使用areContentsTheSame()方法来确定对象是否已更改 - 以及它是否具有RecyclerView的项目将被更新。

Anyway, what I usually prefer is one method which allows me to replace all items in the RecyclerView at once.无论如何,我通常更喜欢一种方法,它允许我一次替换RecyclerView中的所有项目。 Remove everything which is not in the List and add all items which are missing from the SortedList :删除不在List所有内容并添加SortedList中缺少的所有项目:

public void replaceAll(List<ExampleModel> models) {
    mSortedList.beginBatchedUpdates();
    for (int i = mSortedList.size() - 1; i >= 0; i--) {
        final ExampleModel model = mSortedList.get(i);
        if (!models.contains(model)) {
            mSortedList.remove(model);
        }
    }
    mSortedList.addAll(models);
    mSortedList.endBatchedUpdates();
}

This method again batches all updates together to increase performance.此方法再次将所有更新一起批处理以提高性能。 The first loop is in reverse since removing an item at the start would mess up the indexes of all items that come up after it and this can lead in some instances to problems like data inconsistencies.第一个循环是相反的,因为在开始时删除一个项目会弄乱它之后出现的所有项目的索引,这在某些情况下会导致数据不一致等问题。 After that we just add the List to the SortedList using addAll() to add all items which are not already in the SortedList and - just like I described above - update all items that are already in the SortedList but have changed.之后,我们只需使用addAll()List添加到SortedList以添加所有不在SortedList中的项目,并且 - 就像我上面描述的那样 - 更新已经在SortedList但已更改的所有项目。

And with that the Adapter is complete.这样Adapter就完成了。 The whole thing should look something like this:整个事情应该是这样的:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

    private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
        @Override
        public int compare(ExampleModel a, ExampleModel b) {
            return mComparator.compare(a, b);
        }

        @Override
        public void onInserted(int position, int count) {
            notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count) {
            notifyItemRangeChanged(position, count);
        }

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

        @Override
        public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
            return item1 == item2;
        }
    });

    private final Comparator<ExampleModel> mComparator;
    private final LayoutInflater mInflater;

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
        mInflater = LayoutInflater.from(context);
        mComparator = comparator;
    }

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final ItemExampleBinding binding = ItemExampleBinding.inflate(mInflater, parent, false);
        return new ExampleViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) {
        final ExampleModel model = mSortedList.get(position);
        holder.bind(model);
    }

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

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

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

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

    public void replaceAll(List<ExampleModel> models) {
        mSortedList.beginBatchedUpdates();
        for (int i = mSortedList.size() - 1; i >= 0; i--) {
            final ExampleModel model = mSortedList.get(i);
            if (!models.contains(model)) {
                mSortedList.remove(model);
            }
        }
        mSortedList.addAll(models);
        mSortedList.endBatchedUpdates();
    }

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

The only thing missing now is to implement the filtering!现在唯一缺少的是实现过​​滤!


Implementing the filter logic实现过滤器逻辑

To implement the filter logic we first have to define a List of all possible models.要实现过滤器逻辑,我们首先必须定义所有可能模型的List For this example I create a List of ExampleModel instances from an array of movies:在这个例子中,我从一个电影数组中创建了一个ExampleModel实例List

private static final String[] MOVIES = new String[]{
        ...
};

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

private ExampleAdapter mAdapter;
private List<ExampleModel> mModels;
private RecyclerView mRecyclerView;

    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    mAdapter = new ExampleAdapter(this, ALPHABETICAL_COMPARATOR);

    mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
    mBinding.recyclerView.setAdapter(mAdapter);

    mModels = new ArrayList<>();
    for (String movie : MOVIES) {
        mModels.add(new ExampleModel(movie));
    }
    mAdapter.add(mModels);
}

Nothing special going on here, we just instantiate the Adapter and set it to the RecyclerView .这里没什么特别的,我们只是实例化Adapter并将其设置为RecyclerView After that we create a List of models from the movie names in the MOVIES array.之后,我们从MOVIES数组中的电影名称创建一个模型List Then we add all the models to the SortedList .然后我们将所有模型添加到SortedList

Now we can go back to onQueryTextChange() which we defined earlier and start implementing the filter logic:现在我们可以回到我们之前定义的onQueryTextChange()并开始实现过滤器逻辑:

@Override
public boolean onQueryTextChange(String query) {
    final List<ExampleModel> filteredModelList = filter(mModels, query);
    mAdapter.replaceAll(filteredModelList);
    mBinding.recyclerView.scrollToPosition(0);
    return true;
}

This is again pretty straight forward.这再次非常简单。 We call the method filter() and pass in the List of ExampleModel s as well as the query string.我们调用方法filter()并传入ExampleModelList以及查询字符串。 We then call replaceAll() on the Adapter and pass in the filtered List returned by filter() .然后我们在Adapter上调用replaceAll()并传入filter()返回的过滤List We also have to call scrollToPosition(0) on the RecyclerView to ensure that the user can always see all items when searching for something.我们还必须在RecyclerView上调用scrollToPosition(0)以确保用户在搜索某些内容时始终可以看到所有项目。 Otherwise the RecyclerView might stay in a scrolled down position while filtering and subsequently hide a few items.否则RecyclerView在过滤时可能会停留在向下滚动的位置并随后隐藏一些项目。 Scrolling to the top ensures a better user experience while searching.滚动到顶部可确保在搜索时获得更好的用户体验。

The only thing left to do now is to implement filter() itself:现在唯一要做的就是实现filter()本身:

private static List<ExampleModel> filter(List<ExampleModel> models, String query) {
    final String lowerCaseQuery = query.toLowerCase();

    final List<ExampleModel> filteredModelList = new ArrayList<>();
    for (ExampleModel model : models) {
        final String text = model.getText().toLowerCase();
        if (text.contains(lowerCaseQuery)) {
            filteredModelList.add(model);
        }
    }
    return filteredModelList;
}

The first thing we do here is call toLowerCase() on the query string.我们在这里做的第一件事是在查询字符串上调用toLowerCase() We don't want our search function to be case sensitive and by calling toLowerCase() on all strings we compare we can ensure that we return the same results regardless of case.我们不希望我们的搜索函数区分大小写,通过对所有比较的字符串调用toLowerCase() ,我们可以确保无论大小写都返回相同的结果。 It then just iterates through all the models in the List we passed into it and checks if the query string is contained in the text of the model.然后它只是遍历我们传递给它的List中的所有模型,并检查查询字符串是否包含在模型的文本中。 If it is then the model is added to the filtered List .如果是,则将模型添加到过滤后的List

And that's it!就是这样! The above code will run on API level 7 and above and starting with API level 11 you get item animations for free!上面的代码将在 API 级别 7 及更高级别上运行,从 API 级别 11 开始,您可以免费获得项目动画!

I realize that this is a very detailed description which probably makes this whole thing seem more complicated than it really is, but there is a way we can generalize this whole problem and make implementing an Adapter based on a SortedList much simpler.我意识到这是一个非常详细的描述,这可能使整个事情看起来比实际更复杂,但是有一种方法可以概括整个问题并使基于SortedListAdapter实现更简单。


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

  • We need to create a ViewModel interface which all model classes have to implement我们需要创建一个所有模型类都必须实现的ViewModel接口
  • We need to create a ViewHolder subclass which defines a bind() method the Adapter can use to bind models automatically.我们需要创建一个ViewHolder子类,它定义了Adapter可以用来自动绑定模型的bind()方法。

This allows us to just focus on the content which is supposed to be displayed in the RecyclerView by just implementing the models and there corresponding ViewHolder implementations.这允许我们只通过实现模型和相应的ViewHolder实现来专注于应该在RecyclerView显示的内容。 Using this base class we don't have to worry about the intricate details of the Adapter and its SortedList .使用这个基类,我们不必担心Adapter及其SortedList的复杂细节。

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 .由于 StackOverflow 上答案的字符限制,我无法完成实现此基类的每个步骤,甚至无法在此处添加完整的源代码,但您可以找到此基类的完整源代码 - 我将其称为SortedListAdapter - 在这个GitHub 要点

To make your life simple I have published a library on jCenter which contains the SortedListAdapter !为了让您的生活更简单,我在 jCenter 上发布了一个包含SortedListAdapter的库! If you want to use it then all you need to do is add this dependency to your app's build.gradle file:如果您想使用它,那么您需要做的就是将此依赖项添加到您的应用程序的 build.gradle 文件中:

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

You can find more information about this library on the library homepage .您可以在图书馆主页上找到有关该图书馆的更多信息。

Using the SortedListAdapter使用 SortedListAdapter

To use the SortedListAdapter we have to make two changes:要使用SortedListAdapter我们必须进行两项更改:

  • Change the ViewHolder so that it extends SortedListAdapter.ViewHolder .更改ViewHolder使其扩展SortedListAdapter.ViewHolder The type parameter should be the model which should be bound to this ViewHolder - in this case ExampleModel .类型参数应该是应该绑定到这个ViewHolder的模型 - 在这种情况下ExampleModel You have to bind data to your models in performBind() instead of bind() .您必须在performBind()而不是bind()中将数据绑定到您的模型。

     public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> { private final ItemExampleBinding mBinding; public ExampleViewHolder(ItemExampleBinding binding) { super(binding.getRoot()); mBinding = binding; } @Override protected void performBind(ExampleModel item) { mBinding.setModel(item); } }
  • Make sure that all your models implement the ViewModel interface:确保您的所有模型都实现了ViewModel接口:

     public class ExampleModel implements SortedListAdapter.ViewModel { ... }

After that we just have to update the ExampleAdapter to extend SortedListAdapter and remove everything we don't need anymore.之后,我们只需更新ExampleAdapter以扩展SortedListAdapter并删除我们不再需要的所有内容。 The type parameter should be the type of model you are working with - in this case ExampleModel . type 参数应该是您正在使用的模型类型 - 在本例中为ExampleModel But if you are working with different types of models then set the type parameter to ViewModel .但是,如果您使用不同类型的模型,则将 type 参数设置为ViewModel

public class ExampleAdapter extends SortedListAdapter<ExampleModel> {

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

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

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

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

After that we are done!之后我们就完成了! However one last thing to mention: The SortedListAdapter does not have the same add() , remove() or replaceAll() methods our original ExampleAdapter had.然而,最后要提到的一件事是: SortedListAdapter没有与我们原来的ExampleAdapter相同的add()remove()replaceAll()方法。 It uses a separate Editor object to modify the items in the list which can be accessed through the edit() method.它使用单独的Editor对象来修改可以通过edit()方法访问的列表中的项目。 So if you want to remove or add items you have to call edit() then add and remove the items on this Editor instance and once you are done, call commit() on it to apply the changes to the SortedList :因此,如果您想删除或添加项目,您必须调用edit()然后添加和删除此Editor实例上的项目,完成后,对其调用commit()以将更改应用于SortedList

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

All changes you make this way are batched together to increase performance.您以这种方式进行的所有更改都将一起批处理以提高性能。 The replaceAll() method we implemented in the chapters above is also present on this Editor object:我们在上面章节中实现的replaceAll()方法也存在于这个Editor对象中:

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

If you forget to call commit() then none of your changes will be applied!如果您忘记调用commit()则不会应用任何更改!

All you need to do is to add filter method in RecyclerView.Adapter :您需要做的就是在RecyclerView.Adapter添加filter方法:

public void filter(String text) {
    items.clear();
    if(text.isEmpty()){
        items.addAll(itemsCopy);
    } else{
        text = text.toLowerCase();
        for(PhoneBookItem item: itemsCopy){
            if(item.name.toLowerCase().contains(text) || item.phone.toLowerCase().contains(text)){
                items.add(item);
            }
        }
    }
    notifyDataSetChanged();
}

itemsCopy is initialized in adapter's constructor like itemsCopy.addAll(items) . itemsCopy在适配器的构造函数中初始化,如itemsCopy.addAll(items)

If you do so, just call filter from OnQueryTextListener :如果这样做,只需从OnQueryTextListener调用filter

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

    @Override
    public boolean onQueryTextChange(String newText) {
        adapter.filter(newText);
        return true;
    }
});

It's an example from filtering my phonebook by name and phone number.这是按姓名和电话号码过滤我的电话簿的示例。

Following @Shruthi Kamoji in a cleaner way, we can just use a filterable, its meant for that:以更简洁的方式关注@Shruthi Kamoji,我们可以只使用一个可过滤的,它的意思是:

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

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

    ...

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

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

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

                return results;
            }
        };
    }

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

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

The E here is a Generic Type, you can extend it using your class:这里的 E 是一个泛型类型,你可以使用你的类来扩展它:

public class customerAdapter extends GenericRecycleAdapter<CustomerModel>

Or just change the E to the type you want ( <CustomerModel> for example)或者只是将 E 更改为您想要的类型(例如<CustomerModel>

Then from searchView (the widget you can put on menu.xml):然后从 searchView(您可以放在 menu.xml 上的小部件):

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

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

In Adapter:在适配器中:

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

In Activity:在活动中:

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

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

simply create two list in adapter one orignal and one temp and implements Filterable .只需在适配器中创建两个列表,一个 orignal 和一个 temp 并实现 Filterable

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

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

where在哪里

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

With Android Architecture Components through the use of LiveData this can be easily implemented with any type of Adapter .通过使用LiveData 的Android 架构组件,这可以通过任何类型的Adapter轻松实现。 You simply have to do the following steps:您只需执行以下步骤:

1. Setup your data to return from the Room Database as LiveData as in the example below: 1.设置您的数据从房间数据库作为LiveData返回,如下例所示:

@Dao
public interface CustomDAO{

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

2. Create a ViewModel object to update your data live through a method that will connect your DAO and your UI 2.创建一个ViewModel对象,通过连接DAOUI的方法实时更新数据

public class CustomViewModel extends AndroidViewModel {

    private final AppDatabase mAppDatabase;

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

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

}

3. Call your data from the ViewModel on the fly by passing in the query through onQueryTextListener as below: 3.通过 onQueryTextListener 传入查询,即时从ViewModel调用您的数据,如下所示:

Inside onCreateOptionsMenu set your listener as followsonCreateOptionsMenu里面设置你的监听器如下

searchView.setOnQueryTextListener(onQueryTextListener);

Setup your query listener somewhere in your SearchActivity class as follows在 SearchActivity 类中的某处设置查询侦听器,如下所示

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

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

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

Note : Steps (1.) and (2.) are standard AAC ViewModel and DAO implementation, the only real "magic" going on here is in the OnQueryTextListener which will update the results of your list dynamically as the query text changes.注意:步骤 (1.) 和 (2.) 是标准的AAC ViewModelDAO实现,这里唯一真正的“魔法”是在OnQueryTextListener 中,它会随着查询文本的变化动态更新列表的结果。

If you need more clarification on the matter please don't hesitate to ask.如果您需要有关此事的更多说明,请随时提出。 I hope this helped :).我希望这有帮助:)。

I have solved the same problem using the link with some modifications in it.我已经使用链接解决了同样的问题,并对其进行了一些修改。 Search filter on RecyclerView with Cards. RecyclerView with Cards 上的搜索过滤器。 Is it even possible? 甚至有可能吗? (hope this helps). (希望这可以帮助)。

Here is my adapter class这是我的适配器类

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

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


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

   // other overrided methods

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

//Filter class //过滤器类

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


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

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

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

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

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

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

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

} }

//Activity class //活动类

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

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

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

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



    return true;
}
}

In OnQueryTextChangeListener() method use your adapter.在 OnQueryTextChangeListener() 方法中使用您的适配器。 I have casted it to fragment as my adpter is in fragment.我已将它转换为片段,因为我的适配器在片段中。 You can use the adapter directly if its in your activity class.如果适配器在您的活动类中,您可以直接使用它。

This is my take on expanding @klimat answer to not losing filtering animation.这是我对扩展@klimat 答案以不丢失过滤动画的看法。

public void filter(String query){
    int completeListIndex = 0;
    int filteredListIndex = 0;
    while (completeListIndex < completeList.size()){
        Movie item = completeList.get(completeListIndex);
        if(item.getName().toLowerCase().contains(query)){
            if(filteredListIndex < filteredList.size()) {
                Movie filter = filteredList.get(filteredListIndex);
                if (!item.getName().equals(filter.getName())) {
                    filteredList.add(filteredListIndex, item);
                    notifyItemInserted(filteredListIndex);
                }
            }else{
                filteredList.add(filteredListIndex, item);
                notifyItemInserted(filteredListIndex);
            }
            filteredListIndex++;
        }
        else if(filteredListIndex < filteredList.size()){
            Movie filter = filteredList.get(filteredListIndex);
            if (item.getName().equals(filter.getName())) {
                filteredList.remove(filteredListIndex);
                notifyItemRemoved(filteredListIndex);
            }
        }
        completeListIndex++;
    }
}

Basically what it does is looking through a complete list and adding/removing items to a filtered list one by one.基本上它所做的是查看一个完整的列表并将项目一项一项地添加/删除到过滤列表中。

带有 searchview 和 clicklistener 的 Recyclerview

Add an interface in your adapter.在您的适配器中添加一个接口。

public interface SelectedUser{

    void selectedUser(UserModel userModel);

}

implement the interface in your mainactivity and override the method.在您的主要活动中实现接口并覆盖该方法。 @Override public void selectedUser(UserModel userModel) { @Override public void selectedUser(UserModel userModel) {

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



}

Full tutorial and source code: Recyclerview with searchview and onclicklistener完整教程和源代码: Recyclerview with searchview 和 onclicklistener

I don't know why everyone is using 2 copies of the same list to solve this.我不知道为什么每个人都使用同一个列表的 2 个副本来解决这个问题。 This uses too much RAM...这使用了太多的内存...

Why not just hide the elements that are not found, and simply store their index in a Set to be able to restore them later?为什么不直接隐藏未找到的元素,并简单地将它们的索引存储在一个Set以便以后能够恢复它们? That's much less RAM especially if your objects are quite large. RAM要少得多,特别是如果您的对象非常大。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

And I simply have an EditText as my search box:我只有一个EditText作为我的搜索框:

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

            }

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

            }

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

Result:结果:

搜索示例 gif

I recommend modify the solution of @Xaver Kapeller with 2 things below to avoid a problem after you cleared the searched text (the filter didn't work anymore) due to the list back of adapter has smaller size than filter list and the IndexOutOfBoundsException happened.我建议使用以下 2 件事修改 @Xaver Kapeller 的解决方案,以避免在清除搜索文本(过滤器不再工作)后出现问题,因为适配器背面的列表大小小于过滤器列表,并且发生了 IndexOutOfBoundsException。 So the code need to modify as below所以代码需要修改如下

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

And modify also in moveItem functionality并在 moveItem 功能中进行修改

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

Hope that It could help you!希望它可以帮助你!

If you want to search on button click then this works fine.如果你想搜索按钮点击然后这工作正常。

filterIcon.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String strCHR = homeSearchEdit.getText().toString();
        if (homeSearchEdit.getText().toString().length() > 0) {
            ArrayList<ServiceModel> listNew = new ArrayList<>();
            for (int l = 0; l < arrayList.size(); l++) {
                String serviceName = arrayList.get(l).getServiceName().toLowerCase();
                if (serviceName.contains(strCHR.toLowerCase())) {
                    listNew.add(arrayList.get(l));
                }
            }
            recyclerView.setVisibility(View.VISIBLE);
            adapter = new ServiceAdapter(HomeActivity.this, listNew);
            recyclerView.setAdapter(adapter);
        } else {
            recyclerView.setVisibility(View.VISIBLE);
            adapter = new ServiceAdapter(HomeActivity.this, arrayList);
            recyclerView.setAdapter(adapter);
        }
    }
});

where , filterIcon is button and homeSearchEdit is editText(where we apply for search).其中filterIcon是按钮, homeSearchEdit是 editText(我们申请搜索的地方)。

Android has provided DiffUtil.Callback() and DiffUtil.ItemCallback<T> and they help us filter our recycler view nicely Android 提供了DiffUtil.Callback()DiffUtil.ItemCallback<T> ,它们帮助我们很好地过滤我们的回收器视图

DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one. DiffUtil 是一个实用程序类,它计算两个列表之间的差异并输出一个更新操作列表,将第一个列表转换为第二个列表。

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

DiffUtil.Callback() is used with RecyclerView.Adapter DiffUtil.Callback() 与 RecyclerView.Adapter 一起使用

and

DiffUtil.ItemCallback is used with ListAdapter DiffUtil.ItemCallback 与 ListAdapter 一起使用

Filter using RecyclerView使用 RecyclerView 过滤

Create your RecyclerView Like you normally would overriding the创建你的 RecyclerView 就像你通常会覆盖

onCreateViewHolder

onBindViewHolder

getItemCount

and extending the RecyclerView.ViewHolder Class并扩展RecyclerView.ViewHolder Class

Just Like you have done(This is the Kotlin version of snippets from your code)就像您所做的一样(这是您的代码片段的 Kotlin 版本)

override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder? {
    val v: View = LayoutInflater.from(viewGroup.context)
        .inflate(R.layout.recycler_view_card_item, viewGroup, false)
    return ViewHolder(v)
}

fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
    val movie: Movie = mItems.get(i)
    viewHolder.tvMovie.setText(movie.getName())
    viewHolder.tvMovieRating.setText(movie.getRating())
}

override fun getItemCount(): Int {
    return mItems.size()
}

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    var tvMovie: TextView
    var tvMovieRating: TextView

    init {
        tvMovie = itemView.findViewById<View>(R.id.movieName) as TextView
        tvMovieRating = itemView.findViewById<View>(R.id.movieRating) as TextView
    }
}

Now Create another class which will implement the DiffUtil.Callback()现在创建另一个将实现 DiffUtil.Callback() 的类

This class will help convert the recyclerviews currentlist to the filtered list此类将帮助将 recyclerviews currentlist 转换为过滤后的列表

class MoviesDiffUtilCallback(private val oldList: List<Movies>, private val newList: List<Movies>) : DiffUtil.Callback() {

override fun getOldListSize() = oldList.size

override fun getNewListSize() = newList.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = oldList[oldItemPosition].aUniqueId == newList[newItemPosition]. aUniqueId

//aUniqueId-> a field that is unique to each item in your listItems

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = oldList[oldItemPosition] == newList[newItemPosition]

}

In your Activity or Fragment Class Setup your adapter and your filter在您的 Activity 或 Fragment 类中设置您的适配器和过滤器

private fun setupAdapter() {

//mItems is the list you will pass to the adapter
        adapter = CardAdapter(mItems)

        recyclerView.adapter = adapter

}

fun filter(searchText : String){

    val newFilter = mItems.filter {

        it.name.lowercase().contains(text.lowercase()) //filterlogic

    }

//Calculate the list of update operations that can covert one list into the other one 
    val diffResult = DiffUtil.calculateDiff(PostsDiffUtilCallback(mItems,newFilter))

    mItems.clear()


    mItems.addAll(newFilter)

//dispatch all updates to the RecyclerView
    diffResult.dispatchUpdatesTo(adapter)

}

Filter using ListAdapter使用 ListAdapter 过滤

We will be using the filterable interface to help us filter (still figuring out why I shouldn't just use a filter function to get filteredLists and submitList(filteredLists) Directly)我们将使用 filterable 接口来帮助我们进行过滤(仍在弄清楚为什么我不应该只使用过滤器函数来获取过滤列表和 submitList(filteredLists) 直接)

Create your ListAdapter class创建您的 ListAdapter 类

class CardAdapter (
private val mItems : List<Movies>) : ListAdapter<Movies, CardAdapter.BillsPackageViewHolder>(MoviesDiffCallback()),
Filterable {

override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder? {
    val v: View = LayoutInflater.from(viewGroup.context)
        .inflate(R.layout.recycler_view_card_item, viewGroup, false)
    return ViewHolder(v)
}

fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
    val movie: Movie = mItems.get(i)
    viewHolder.tvMovie.setText(movie.getName())
    viewHolder.tvMovieRating.setText(movie.getRating())
}


override fun getFilter(): Filter {

    return object : Filter() {

        override fun performFiltering(constraint: CharSequence?): FilterResults {

            return FilterResults().apply {

                values = if (constraint.isNullOrEmpty())
                    mItems
                else
                    onFilter(mItems, constraint.toString())
            }
        }

        @Suppress("UNCHECKED_CAST")
        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {

            submitList(results?.values as? List<Movies>)

        }
    }

}

fun onFilter(list: List<Movies>, constraint: String) : List<Movies>{

    val filteredList = list.filter {

        it.name.lowercase().contains(constraint.lowercase())

    }

    return filteredList

}

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    var tvMovie: TextView
    var tvMovieRating: TextView

    init {
        tvMovie = itemView.findViewById<View>(R.id.movieName) as TextView
        tvMovieRating = itemView.findViewById<View>(R.id.movieRating) as TextView
    }
}
}

Now Create another class which will implement the DiffUtil.ItemCallback现在创建另一个将实现 DiffUtil.ItemCallback 的类

class MoviesDiffCallback : DiffUtil.ItemCallback<Movies>() {

override fun areItemsTheSame(oldItem: Movies, newItem: Movies): Boolean {
    return oldItem.someUniqueid == newItem.someUniqueid
}

override fun areContentsTheSame(oldItem: Movies, newItem: Movies): Boolean {
    return oldItem == newItem
}
}

and in your MainActivity or Fragment Setup your adapter and your filter在您的 MainActivity 或 Fragment Setup 中,您的适配器和过滤器

private fun setupAdapter() {

    adapter = CardAdapter(mItems)

    recyclerView.adapter = adapter

}

fun filter(searchString : String){

    adapter.filter.filter(searchString)

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM