如何使用 SearchView 过滤 RecyclerView

新手上路,请多包涵

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

到目前为止,我已经学习了一些教程,并且已将 SearchView 添加到 ActionBar ,但我不确定从这里去哪里。我已经看过一些示例,但是当您开始输入时,它们都没有显示结果。

这是我的 MainActivity

 public class MainActivity extends ActionBarActivity {

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

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

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

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

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

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

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

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

        return super.onOptionsItemSelected(item);
    }
}

这是我的 Adapter

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

    List<Movie> mItems;

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

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

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

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

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

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

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

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

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

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

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

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

    class ViewHolder extends RecyclerView.ViewHolder{

        public TextView tvMovie;
        public TextView tvMovieRating;

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

原文由 Jacques Krause 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 705
2 个回答

介绍

由于您的问题并不清楚您到底遇到了什么问题,因此我写了这个关于如何实现此功能的快速演练;如果您仍有疑问,请随时提出。

我在这个 GitHub Repository 中有一个关于我所说的一切的工作示例。

无论如何,结果应该是这样的:

演示图像

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

在 Google Play 上获取

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


设置 SearchView

在文件夹 res/menu 创建一个名为 main_menu.xml 的新文件。在其中添加一个项目并将 actionViewClass 设置为 android.support.v7.widget.SearchView 。由于您使用的是支持库,因此您必须使用支持库的命名空间来设置 actionViewClass 属性。您的 xml 文件应如下所示:

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

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

</menu>

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 并实现 OnQueryTextListener 我们将使用它来监听输入到 SearchView 的文本的变化:

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

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

    return true;
}

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

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

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


设置 Adapter

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

 public class ExampleModel {

    private final long mId;
    private final String mText;

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

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }
}

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

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

    <data>

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

    </data>

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

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

    </FrameLayout>

</layout>

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

这是 ViewHolder 用于 ExampleModel 类:

 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 是一个非常棒的工具,它是 RecyclerView 库的一部分。它负责通知 Adapter 关于数据集的更改,这是一种非常有效的方式。它唯一需要您做的就是指定元素的顺序。您需要通过实现 compare() 方法来比较 --- 中的两个元素,就像 Comparator SortedList 。但不是对 List 进行排序,而是用于对 RecyclerView 中的项目进行排序!

Adapter 通过 Callback 您必须实现的类与 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();
    }
}

在回调顶部的方法中,例如 onMovedonInserted 等,您必须调用 Adapter 的等效通知方法。底部的三个方法 compare , areContentsTheSameareItemsTheSame 你必须根据你想要显示什么样的对象以及这些对象应该以什么顺序来实现出现在屏幕上。

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

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

这就是我前面讲的 compare() 方法。在此示例中,我只是将调用传递给 Comparator 比较两个模型。如果您希望项目按字母顺序显示在屏幕上。此比较器可能如下所示:

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

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

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

此方法的目的是确定模型的内容是否已更改。 SortedList 使用它来确定是否需要调用更改事件 - 换句话说, RecyclerView 是否应该交叉淡入淡出旧版本和新版本。如果您的模型类具有正确的 equals()hashCode() 实现,您通常可以像上面那样实现它。如果我们将 equals()hashCode() 实现添加到 ExampleModel 类,它应该看起来像这样:

 public class ExampleModel implements SortedListAdapter.ViewModel {

    private final long mId;
    private final String mText;

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

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }

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

        ExampleModel model = (ExampleModel) o;

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

    }

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

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

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

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

SortedList 使用这种方法来检查两个项目是否指的是同一个东西。简单来说(不解释 SortedList 是如何工作的),这用于确定对象是否已包含在 List 中,以及是否需要添加、移动或更改动画玩过。如果您的模型有 id,您通常会在此方法中只比较 id。如果他们不这样做,您需要找出其他方法来检查这一点,但是您最终实现这取决于您的特定应用程序。通常,给所有模型一个 id 是最简单的选择——例如,如果您从数据库中查询数据,它可能是主键字段。

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

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

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

现在让我们开始做正事:如果我们用 Adapter 实现 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 即使项目应该以不同的顺序显示。

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

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

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

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

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

我们不需要在这里调用任何通知方法,因为 SortedList 已经通过 SortedList.Callback 执行此操作!除此之外,这些方法的实现非常简单,但有一个例外:remove 方法删除了 List 模型。由于 SortedList 只有一个可以删除单个对象的删除方法,我们需要遍历列表并逐个删除模型。在开始时调用 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 。对于这个例子,我从一系列电影中创建了一个 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 。之后,我们根据 MOVIES 数组中的电影名称创建一个 List 模型。然后我们将所有模型添加到 SortedList

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

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

这又很简单。我们调用方法 filter() 并传入 ListExampleModel 以及查询字符串。然后我们在 replaceAll() Adapter 并传入过滤后的 Listfilter() 返回。我们还必须在 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 开始,您可以免费获得项目动画!

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


泛化问题并简化适配器

在本节中,我不会详细介绍 - 部分原因是我在 Stack Overflow 上遇到了字符数限制,但也因为上面已经解释了大部分内容 - 但总结一下变化:我们可以实现一个基础 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 基于 SortedList 。为此,我们必须做两件事:

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

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

排序列表适配器

由于 StackOverflow 上答案的字符限制,我无法完成实现此基类的每一步,甚至无法在此处添加完整的源代码,但您可以找到此基类的完整源代码——我称之为 SortedListAdapter 在这个 GitHub Gist 中。

为了让您的生活更简单,我在 jCenter 上发布了一个库,其中包含 SortedListAdapter !如果您想使用它,那么您需要做的就是将此依赖项添加到您的应用程序的 build.gradle 文件中:

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

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

使用 SortedListAdapter

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

  • 更改 ViewHolder 使其扩展 SortedListAdapter.ViewHolder 。类型参数应该是应该绑定到这个的模型 ViewHolder - 在这种情况下 ExampleModel 。您必须在 performBind() 而不是 bind() 中将数据绑定到您的模型。
    public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> {

       private final ItemExampleBinding mBinding;

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

       @Override
       protected void performBind(ExampleModel item) {
           mBinding.setModel(item);
       }
   }

  • 确保所有模型都实现了 ViewModel 接口:
    public class ExampleModel implements SortedListAdapter.ViewModel {
       ...
   }

之后,我们只需更新 ExampleAdapter 以扩展 SortedListAdapter 并删除我们不再需要的所有内容。 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);
    }
}

之后我们就完成了! However one last thing to mention: The SortedListAdapter does not have the same add() , remove() or replaceAll() methods our original ExampleAdapter 有。它使用一个单独的 Editor 对象来修改可以通过 edit() 方法访问的列表中的项目。因此,如果您想删除或添加项目,您必须调用 edit() 然后添加和删除此 Editor 实例上的项目,完成后,调用 commit() 在其上将更改应用于 SortedList

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

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

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

如果您忘记拨打 commit() 那么您的任何更改都不会应用!

原文由 Xaver Kapeller 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

DiffUtil 是一个实用程序类,它计算两个列表之间的差异并输出一个更新操作列表,将第一个列表转换为第二个列表。

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

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

DiffUtil.ItemCallback 与 ListAdapter 一起使用

使用 RecyclerView 过滤

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

onCreateViewHolder

onBindViewHolder

getItemCount

并扩展 RecyclerView.ViewHolder Class

就像您所做的一样(这是您的代码片段的 Kotlin 版本)

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

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

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

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

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

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

此类将帮助将 recyclerviews currentlist 转换为过滤后的列表

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

override fun getOldListSize() = oldList.size

override fun getNewListSize() = newList.size

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

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

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

}

在您的 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)

}

使用 ListAdapter 过滤

我们将使用可过滤的接口来帮助我们过滤(仍然在弄清楚为什么我不应该只使用过滤函数来直接获取过滤列表和提交列表(过滤列表))

创建您的 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 Setup 中,您的适配器和过滤器

private fun setupAdapter() {

    adapter = CardAdapter(mItems)

    recyclerView.adapter = adapter

}

fun filter(searchString : String){

    adapter.filter.filter(searchString)

}

原文由 Olayiwola Osho 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题