如何使用SearchView过滤RecyclerView


319

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

到目前为止,我已经关注了一些教程,并且已将教程添加SearchViewActionBar,但是我不确定如何从这里开始。我看到了一些示例,但是当您开始输入时,它们都没有显示结果。

这是我的MainActivity

public class MainActivity extends ActionBarActivity {

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

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

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

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

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

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

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

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

        return super.onOptionsItemSelected(item);
    }
}

这是我的Adapter

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

    List<Movie> mItems;

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

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

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

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

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

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

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

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

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

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

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

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

    class ViewHolder extends RecyclerView.ViewHolder{

        public TextView tvMovie;
        public TextView tvMovieRating;

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

Answers:


913

介绍

由于您的问题并不清楚,您到底在遇到什么问题,因此我撰写了此快速演练,内容涉及如何实现此功能。如果您仍有问题,请随时提出。

在此GitHub存储库中,我有一个正在讨论的所有示例的工作示例。
如果您想了解更多有关示例项目的信息,请访问项目主页

无论如何,结果应如下所示:

演示图片

如果您首先想使用演示应用程序,可以从Play商店安装它:

在Google Play上获取

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


设置 SearchView

在文件夹中res/menu创建一个名为的新文件main_menu.xml。在其中添加一个项目,并将设置actionViewClassandroid.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>

在您中,Fragment或者Activity您必须像往常一样给这个菜单xml充气,然后您可以查找MenuItem其中包含的,SearchView并实现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()一旦完成的实现,我们将在稍后实现过滤器逻辑Adapter


设置 Adapter

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

public class ExampleModel {

    private final long mId;
    private final String mText;

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

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }
}

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

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

    <data>

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

    </data>

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

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

    </FrameLayout>

</layout>

如您所见,我使用数据绑定。如果您从未使用过数据绑定,请不要气disc!它非常简单且功能强大,但是我无法解释它在此答案范围内的工作方式。

这是ViewHolderExampleModel课程的:

public class ExampleViewHolder extends RecyclerView.ViewHolder {

    private final ItemExampleBinding mBinding;

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

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

再次没有什么特别的。正如我们在上面的布局xml中定义的那样,它仅使用数据绑定将模型类绑定到该布局。

现在,我们终于可以进入真正有趣的部分:编写适配器。我将跳过的基本实现,Adapter而是将重点放在与该答案相关的部分上。

但是首先,我们必须谈论一件事:SortedList课堂。


SortedList

SortedList是一个完全令人惊奇的工具,它是该RecyclerView库的一部分。它负责通知Adapter有关数据集的更改,并且这样做非常有效。它唯一需要您做的就是指定元素的顺序。为此,您需要实现一种compare()方法,该方法可以SortedList像一样比较两个元素Comparator。但是,不是对a List进行排序,而是对RecyclerView!中的项目进行排序。

SortedList与交互Adapter,通过一个Callback类,你必须实现:

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

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

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

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

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

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

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

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

在像回调的顶部的方法onMovedonInserted等你要调用相当于通知您的方法Adapter。这三种方法在底部compareareContentsTheSame并且areItemsTheSame你必须根据类型的对象要显示的内容和顺序,这些对象应该出现在屏幕上实现。

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

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

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

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

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

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

此方法的目的是确定模型的内容是否已更改。在SortedList使用它来确定是否改变事件需要调用-换句话说,如果RecyclerView要交叉衰减新旧版本。如果你的模型类有一个正确equals()hashCode()执行通常可以只实现它喜欢上面。如果我们在类中添加equals()hashCode()实现,ExampleModel则应如下所示:

public class ExampleModel implements SortedListAdapter.ViewModel {

    private final long mId;
    private final String mText;

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

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }

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

        ExampleModel model = (ExampleModel) o;

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

    }

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

快速提示:只需按一下按钮,大多数IDE(例如Android Studio,IntelliJ和Eclipse)都具有为您生成equals()hashCode()实现的功能!因此,您不必自己实施它们。在Internet上查找它在IDE中的工作方式!

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

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

SortedList使用此方法来检查,如果两个项目指的是同样的事情。用最简单的术语(无需解释其SortedList工作原理)来确定是否已包含对象,List以及是否需要播放添加,移动或更改动画。如果您的模型有一个ID,通常在此方法中仅比较ID。如果不是这样,则您需要找出其他方法来进行检查,但是最终还是要根据您的特定应用程序来实现。通常,给所有模型一个ID是最简单的选择-例如,如果您要查询数据库中的数据,则可以将其作为主键字段。

有了SortedList.Callback正确实施,我们可以创建的一个实例SortedList

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

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

现在让我们开始谈谈:如果我们使用来实现AdapterSortedList它应该看起来像这样:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

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

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

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

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

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

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

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

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

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

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

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

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

Comparator用来排序的项目是通过构造函数传递,因此可以使用相同的Adapter,即使项目都应该显示在不同的顺序。

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

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

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

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

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

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

此外,您需要了解的是,如果您向中添加了一个对象,SortedList而该对象已经存在于中SortedList,则不会再次添加该对象。而是SortedList使用areContentsTheSame()方法来确定对象是否已更改-并且对象中的项目RecyclerView将被更新。

无论如何,我通常更喜欢的是一种方法,它使我可以一次替换其中的所有项目RecyclerView。删除中没有的所有内容,List并添加缺少的所有项目SortedList

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

此方法再次将所有更新批处理在一起以提高性能。第一个循环是相反的,因为从开始处删除一个项目会弄乱后面所有项目的索引,这在某些情况下可能会导致诸如数据不一致之类的问题。之后,我们只是添加List了对SortedList使用addAll()添加这是不是已经在所有项目SortedList和-就像我上面描述-更新中已经存在的所有物品SortedList,但已经改变。

Adapter完成了。整个过程应如下所示:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


实施过滤逻辑

为了实现过滤器逻辑,我们首先必须定义List所有可能模型的a。对于这个例子中我创建ListExampleModel从电影的阵列实例:

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

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

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

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

    mAdapter = new ExampleAdapter(this, ALPHABETICAL_COMPARATOR);

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

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

这里没有什么特别的事情,我们只需实例化Adapter并将其设置为即可RecyclerView。之后,我们List根据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;
}

这再次很简单。我们调用该方法filter(),并在通过ListExampleModelS以及查询字符串。然后replaceAll(),我们调用,Adapter并传入所List返回的过滤条件filter()。我们还必须调用scrollToPosition(0)RecyclerView以确保用户在搜索内容时始终可以看到所有项目。否则RecyclerView,过滤时可能会保持向下滚动的位置,并随后隐藏一些项目。滚动到顶部可确保在搜索时获得更好的用户体验。

现在剩下要做的唯一一件事就是实现filter()自己:

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

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

我们在这里要做的第一件事是调用toLowerCase()查询字符串。我们不希望我们的搜索功能区分大小写,并且通过调用toLowerCase()所有字符串进行比较,我们可以确保无论大小写均返回相同的结果。然后,它仅遍历List传入的所有模型,并检查查询字符串是否包含在模型文本中。如果是,则将模型添加到已过滤的中List

就是这样!上面的代码将在API级别7和更高级别上运行,从API级别11开始,您将免费获得道具动画!

我意识到这是一个非常详细的描述,可能使得这整个事情看起来比它确实是比较复杂的,但我们可以概括这整个问题,使实施方式的Adapter基础上SortedList要简单得多。


概括问题并简化适配器

在本节中,我将不做详细介绍-部分是因为我在堆栈溢出问题上遇到了字符数限制的问题,还因为上面已经解释了大多数问题-但总结了这些变化:我们可以实现一个基Adapter类已经负责处理实例和SortedList绑定模型的ViewHolder实例,并提供了一种方便的方式实现Adapter基于的实例SortedList。为此,我们必须做两件事:

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

这允许我们仅RecyclerView通过实现模型及其相应的ViewHolder实现,专注于应该在中显示的内容。使用此基类,我们不必担心的详细信息Adapter及其SortedList

SortedListAdapter

由于StackOverflow上答案的字符数限制,我无法遍历实现该基类的每个步骤,甚至无法在此处添加完整的源代码,但是您可以在此找到该基类的完整源代码-我称它为SortedListAdapter- GitHub Gist

为了简化您的生活,我在jCenter上发布了一个包含SortedListAdapter!的库。如果要使用它,则只需将此依赖项添加到应用程序的build.gradle文件中:

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

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

使用SortedListAdapter

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

  • 更改ViewHolder使其扩展SortedListAdapter.ViewHolderViewHolder在这种情况下,类型参数应该是应与此绑定的模型ExampleModel。您必须将数据绑定到模型中,performBind()而不是bind()

    public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> {
    
        private final ItemExampleBinding mBinding;
    
        public ExampleViewHolder(ItemExampleBinding binding) {
            super(binding.getRoot());
            mBinding = binding;
        }
    
        @Override
        protected void performBind(ExampleModel item) {
            mBinding.setModel(item);
        }
    }
  • 确保所有模型都实现该ViewModel接口:

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

之后,我们只需要更新ExampleAdapter即可扩展SortedListAdapter并删除不再需要的所有内容。在这种情况下,type参数应该是您正在使用的模型的类型ExampleModel。但是,如果您使用的是不同类型的模型,则将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);
    }
}

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

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

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

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

如果您忘了打电话,commit()那么您的任何更改都不会被应用!


4
@TiagoOliveira好吧,它是开箱即用的:D数据绑定对于不熟悉它的人来说是一个障碍,但是无论如何我都包含了它,因为它很棒,我想推广它。由于某些原因,似乎没有多少人知道这一点……
Xaver Kapeller '16

78
我尚未阅读完整的答案,我不得不将阅读暂停一半,以撰写此评论-这是我在SO上找到的最佳答案之一!谢谢!
daneejela

16
我只是喜欢你的样子:“不清楚你的问题是什么,所以这是我刚刚做过的完整例子”:D
Fred

7
+1只是向我们展示Android中存在数据绑定!我从来没有听说过,似乎我将开始使用它。谢谢
豪尔赫·卡萨列戈

6
这个解决方案太长了,而且通常来说是过度设计的。去第二个。
Enrico Casini

194

您需要做的就是在中添加filter方法RecyclerView.Adapter

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

itemsCopy在适配器的构造函数中初始化itemsCopy.addAll(items)

如果这样做,只需调用filter来自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;
    }
});

这是通过按名称和电话号码过滤电话簿的示例。


11
我认为这应该是公认的答案。它更简单并且可以正常工作
Jose_GD

6
简单高效!
AlxDroidDev

11
请注意,如果您遵循这种方法而不是@Xaver Kapeller答案,则会丢失动画。
2015年

23
没有尝试接受的答案,因为它太长了。该答案有效且易于实现。不要忘了补充。“应用:actionViewClass =” android.support.v7.widget.SearchView”在你的菜单项XML
SajithK

3
确切的项目和项目在这里复制什么?
Lucky_girl

82

以更简洁的方式跟随@Shruthi Kamoji,我们可以仅使用一个filterable,其目的是:

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

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

    ...

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

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

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

                return results;
            }
        };
    }

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

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

这里的E是通用类型,您可以使用您的类扩展它:

public class customerAdapter extends GenericRecycleAdapter<CustomerModel>

或者只是将E更改为所需的类型(<CustomerModel>例如)

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

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

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

我用这样的东西!可以正常工作的样本!
Mateus

你好,谁可以帮助我一步-Y-一步与这一个:stackoverflow.com/questions/40754174/...
托瓦尔Olavsen

最干净的答案!
adalpari

4
这比投票表决的答案好得多,因为该操作是在performFiltering方法中的工作线程上完成的。

1
但是,您将对同一列表的引用分配给不同的变量。例如this.originalList = list; 您应该改用addAll或在ArrayList构造函数中传递列表
Florian Walther

5

只需在适配器一个原始的和一个临时的适配器中创建两个列表,并实现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();
            }
        };
    }

哪里

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

不错的解决方案。我创建了两个列表,并使用了一种简单的过滤方法。我似乎无法将项目的正确适配器位置传递给下一个活动。我将不胜感激您对此提出的任何想法或想法: stackoverflow.com/questions/46027110/…–
AJW

4

在适配器中:

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

活动中:

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

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

3

通过使用LiveData使用Android体系结构组件,可以使用任何类型的Adapter轻松实现该功能。您只需要执行以下步骤:

1.设置数据以从Room 数据库作为LiveData返回,如下例所示:

@Dao
public interface CustomDAO{

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

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.通过如下所示通过onQueryTextListener传递查询,从ViewModel中即时调用数据:

onCreateOptionsMenu设置您的听众,如下

searchView.setOnQueryTextListener(onQueryTextListener);

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

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

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

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

注意:步骤(1.)和(2.)是标准的AAC ViewModelDAO实现,唯一真正的“魔术”是在OnQueryTextListener中,它将随着查询文本的更改动态更新列表的结果。

如果您需要对此事进行更多说明,请随时询问。希望对您有所帮助:)。


1

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

基本上,它的工作是浏览完整的列表,然后逐项添加/删除项目到过滤列表中。


0

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

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

并在moveItem功能中进行修改

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

希望它可以帮助您!


这根本没有必要。
Xaver Kapeller

对于原始答案,如果您不这样做,将会发生IndexOutOfBoundsException,那么为什么没有必要?你要日志吗?@XaverKapeller
toidv

仅当您Adapter以错误的方式实现时,例外才会发生。没有看到您的代码,我想最可能的问题是您没有将包含所有项目的列表的副本传递给Adapter
Xaver Kapeller

错误日志:W / System.err:java.lang.IndexOutOfBoundsException:无效的索引36,大小为35 W / System.err:在java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)W / System.err:在java.util.ArrayList.add(ArrayList.java:147)W / System.err:在com.quomodo.inploi.ui.adapter.MultipleSelectFilterAdapter.addItem(MultipleSelectFilterAdapter.java:125)W / System.err:在com .quomodo.inploi.ui.adapter.MultipleSelectFilterAdapter.applyAndAnimateAdditions(MultipleSelectFilterAdapter.java:78)
toidv

请帮助检查下面的源代码@XaverKapeller gist.github.com/toidv/fe71dc45169e4138271b52fdb29420c5
toidv '16

0

具有searchview和clicklistener的Recyclerview

在适配器中添加接口。

public interface SelectedUser{

    void selectedUser(UserModel userModel);

}

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

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



}

完整的教程和源代码: 带有searchview和onclicklistener的Recyclerview


-1

使用链接进行了一些修改,我已经解决了相同的问题。使用卡片在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();
}

}

//活动类

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()方法中,使用您的适配器。我将其转换为碎片,因为我的适配器处于碎片状态。如果适配器在活动类中,则可以直接使用适配器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.