我试图实现从支持库的SearchView。我想让用户使用SearchView来过滤一个RecyclerView中的电影列表。

到目前为止,我已经遵循了一些教程,我已经添加了搜索视图到动作栏,但我真的不确定从这里去哪里。我看过一些例子,但没有一个在你开始输入时显示结果。

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

这是我的适配器:

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

当前回答

Android提供了DiffUtil. callback()和DiffUtil. callback()。ItemCallback<T>它们帮助我们很好地过滤回收视图

DiffUtil是一个计算两者之差的实用程序类 列出并输出转换第一个更新操作的列表 列表到第二个。 https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil

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

and

DiffUtil。ItemCallback与ListAdapter一起使用

使用RecyclerView过滤

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

onCreateViewHolder

onBindViewHolder

getItemCount

和扩展RecyclerView。ViewHolder类

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

}

其他回答

简介

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

在这个GitHub知识库中,我有一个我在这里谈论的所有事情的工作示例。

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

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

不管怎样,我们开始吧。


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

在你的片段或活动中,你必须像往常一样膨胀这个菜单xml,然后你可以寻找包含SearchView的MenuItem,并实现OnQueryTextListener,我们将使用它来监听输入到SearchView的文本的变化:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);

    final MenuItem searchItem = menu.findItem(R.id.action_search);
    final SearchView searchView = (SearchView) searchItem.getActionView();
    searchView.setOnQueryTextListener(this);

    return true;
}

@Override
public boolean onQueryTextChange(String query) {
    // Here is where we are going to implement the filter logic
    return false;
}

@Override
public boolean onQueryTextSubmit(String query) {
    return false;
}

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


设置适配器

首先,这是我将在这个例子中使用的模型类:

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>

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

这是exampleemodel类的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中定义的那样。

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

但首先我们必须讨论一件事:SortedList类。


SortedList

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

SortedList通过回调类与适配器交互,你必须实现:

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

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

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

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

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

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

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

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

在回调顶部的方法中,如onMoved, onInserted等,你必须调用适配器的等效notify方法。底部的三个方法比较,arecontentssame和areitemssame你必须根据你想要显示的对象类型和这些对象应该以什么顺序出现在屏幕上来实现。

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

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

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

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()实现,你通常可以像上面那样实现它。如果我们在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;
    }
}

快速边注:大多数IDE,如Android Studio, IntelliJ和Eclipse都有功能生成equals()和hashCode()实现,只需按下按钮!所以你不需要自己实现它们。在互联网上查找它是如何在您的IDE中工作的!

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

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

The SortedList uses this method to check if two items refer to the same thing. In simplest terms (without explaining how the SortedList works) this is used to determine if an object is already contained in the List and if either an add, move or change animation needs to be played. If your models have an id you would usually compare just the id in this method. If they don't you need to figure out some other way to check this, but however you end up implementing this depends on your specific app. Usually it is the simplest option to give all models an id - that could for example be the primary key field if you are querying the data from a database.

使用SortedList。我们可以创建一个SortedList的实例:

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

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

现在让我们进入正事:如果我们用SortedList实现适配器,它应该看起来像这样:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

We don't need to call any notify methods here because the SortedList already does this for through the SortedList.Callback! Aside from that the implementation of these methods is pretty straight forward with one exception: the remove method which removes a List of models. Since the SortedList has only one remove method which can remove a single object we need to loop over the list and remove the models one by one. Calling beginBatchedUpdates() at the beginning batches all the changes we are going to make to the SortedList together and improves performance. When we call endBatchedUpdates() the RecyclerView is notified about all the changes at once.

此外,你必须理解的是,如果你添加一个对象到SortedList,它已经在SortedList中,它不会再被添加。相反,SortedList使用arecontentssame()方法来确定对象是否发生了变化——如果发生了变化,则RecyclerView中的项将被更新。

无论如何,我通常更喜欢的是一种方法,它允许我一次替换RecyclerView中的所有项目。删除列表中没有的所有内容,并添加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();
}

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

这样适配器就完成了。整个过程应该是这样的:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

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

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

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

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

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

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

        @Override
        public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
            return item1 == 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实例列表:

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

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

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

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

This is again pretty straight forward. We call the method filter() and pass in the List of ExampleModels as well as the query string. We then call replaceAll() on the Adapter and pass in the filtered List returned by filter(). We also have to call scrollToPosition(0) on the RecyclerView to ensure that the user can always see all items when searching for something. Otherwise the RecyclerView might stay in a scrolled down position while filtering and subsequently hide a few items. Scrolling to the top ensures a better user experience while searching.

现在唯一要做的就是实现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开始,你可以免费获得项目动画!

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


泛化问题并简化适配器

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

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

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

SortedListAdapter

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

为了让您的生活变得简单,我在jCenter上发布了一个库,其中包含SortedListAdapter!如果你想使用它,那么你所需要做的就是将这个依赖添加到应用程序的构建中。gradle文件:

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

你可以在图书馆主页上找到更多关于这个图书馆的信息。

使用SortedListAdapter

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

Change the ViewHolder so that it extends SortedListAdapter.ViewHolder. The type parameter should be the model which should be bound to this ViewHolder - in this case ExampleModel. You have to bind data to your models in performBind() instead of bind(). public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> { private final ItemExampleBinding mBinding; public ExampleViewHolder(ItemExampleBinding binding) { super(binding.getRoot()); mBinding = binding; } @Override protected void performBind(ExampleModel item) { mBinding.setModel(item); } } Make sure that all your models implement the ViewModel interface: public class ExampleModel implements SortedListAdapter.ViewModel { ... }

在此之后,我们只需更新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()将更改应用到SortedList:

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

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

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

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

你所需要做的就是在RecyclerView中添加过滤器方法。适配器:

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

itemscope在适配器的构造函数中初始化,如itemscope . addall (items)。

如果你这样做,只是调用过滤器从OnQueryTextListener:

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

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

我已经解决了同样的问题使用链接与一些修改。用卡片在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()方法中使用适配器。我已经把它铸造成碎片,因为我的适配器是碎片。如果它在您的活动类中,您可以直接使用适配器。

简单地创建两个列表在适配器一个原始和一个临时和实现可过滤。

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

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

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