Как отфильтровать RecyclerView с помощью SearchView
Я пытаюсь реализовать SearchView
из библиотеки поддержки. Я хочу, чтобы пользователь использовал SearchView
фильтровать List
фильмов в RecyclerView
,
До сих пор я следовал нескольким учебникам и добавил SearchView
к ActionBar
, но я не совсем уверен, куда идти отсюда. Я видел несколько примеров, но ни один из них не показывает результаты, когда вы начинаете печатать.
Это мое MainActivity
:
public class MainActivity extends ActionBarActivity {
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
RecyclerView.Adapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new CardAdapter() {
@Override
public Filter getFilter() {
return null;
}
};
mRecyclerView.setAdapter(mAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
А это мой Adapter
:
public abstract class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> implements Filterable {
List<Movie> mItems;
public CardAdapter() {
super();
mItems = new ArrayList<Movie>();
Movie movie = new Movie();
movie.setName("Spiderman");
movie.setRating("92");
mItems.add(movie);
movie = new Movie();
movie.setName("Doom 3");
movie.setRating("91");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers");
movie.setRating("88");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 2");
movie.setRating("87");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 3");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Noah");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 2");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 3");
movie.setRating("86");
mItems.add(movie);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
Movie movie = mItems.get(i);
viewHolder.tvMovie.setText(movie.getName());
viewHolder.tvMovieRating.setText(movie.getRating());
}
@Override
public int getItemCount() {
return mItems.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
public TextView tvMovie;
public TextView tvMovieRating;
public ViewHolder(View itemView) {
super(itemView);
tvMovie = (TextView)itemView.findViewById(R.id.movieName);
tvMovieRating = (TextView)itemView.findViewById(R.id.movieRating);
}
}
}
13 ответов
Вступление
Поскольку из вашего вопроса не совсем ясно, с чем именно у вас возникают проблемы, я написал краткое руководство о том, как реализовать эту функцию; если у вас все еще есть вопросы, не стесняйтесь спрашивать.
У меня есть рабочий пример всего, о чем я говорю здесь, в этом GitHub Repository.
Если вы хотите узнать больше о примере проекта, посетите домашнюю страницу проекта.
В любом случае результат должен выглядеть примерно так:
Если вы сначала хотите поиграть с демо-приложением, вы можете установить его из 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>
В вашем 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>
Как видите, я использую привязку данных. Если вы никогда не работали с привязкой данных, не расстраивайтесь! Это очень просто и мощно, однако я не могу объяснить, как это работает в рамках этого ответа.
Это 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
SortedList
это совершенно удивительный инструмент, который является частью RecyclerView
библиотека. Он заботится об уведомлении Adapter
об изменениях в наборе данных и делает это очень эффективным способом. Единственное, что от вас требуется, это указать порядок элементов. Вы должны сделать это путем реализации compare()
метод, который сравнивает два элемента в SortedList
совсем как Comparator
, Но вместо сортировки 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();
}
}
В методах в верхней части обратного вызова, как onMoved
, onInserted
и т. д. вы должны вызвать эквивалентный метод уведомления вашего Adapter
, Три метода внизу compare
, areContentsTheSame
а также areItemsTheSame
Вы должны реализовать в соответствии с тем, какие объекты вы хотите отобразить и в каком порядке эти объекты должны отображаться на экране.
Давайте рассмотрим эти методы один за другим:
@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
и если необходимо воспроизвести анимацию добавления, перемещения или изменения. Если ваши модели имеют идентификатор, вы обычно сравниваете только идентификатор в этом методе. Если это не так, вам нужно найти какой-то другой способ проверить это, но, тем не менее, вы в конечном итоге реализуете это, зависит от вашего конкретного приложения. Обычно это самый простой способ присвоить идентификаторы всем моделям - например, это может быть поле первичного ключа, если вы запрашиваете данные из базы данных.
С 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
из всех возможных моделей. Для этого примера я создаю 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);
}
Ничего особенного здесь не происходит, мы просто создаем 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()
и пройти в List
из ExampleModel
s, а также строка запроса. Затем мы позвоним 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 и выше, а начиная с уровня 11 API, вы получаете анимацию предметов бесплатно!
Я понимаю, что это очень подробное описание, которое, вероятно, делает все это более сложным, чем оно есть на самом деле, но есть способ, которым мы можем обобщить всю эту проблему и сделать реализацию Adapter
основанный на SortedList
намного проще
Обобщение проблемы и упрощение адаптера
В этом разделе я не буду вдаваться в подробности - отчасти потому, что я сталкиваюсь с лимитом символов для ответов по переполнению стека, а также потому, что большая часть этого уже объяснена выше, - но суммирую изменения: мы можем реализовать базу Adapter
класс, который уже занимается борьбой с SortedList
а также обязательные модели для ViewHolder
экземпляры и обеспечивает удобный способ реализации Adapter
основанный на SortedList
, Для этого мы должны сделать две вещи:
- Нам нужно создать
ViewModel
интерфейс, который должны реализовывать все классы моделей - Нам нужно создать
ViewHolder
подкласс, который определяетbind()
методAdapter
можно использовать для автоматического связывания моделей.
Это позволяет нам просто сосредоточиться на контенте, который должен отображаться в RecyclerView
просто реализуя модели и соответствующие ViewHolder
Реализации. Используя этот базовый класс, нам не нужно беспокоиться о сложных деталях Adapter
И его SortedList
,
SortedListAdapter
Из-за ограничения символов для ответов в Stack Overflow я не могу пройти каждый шаг реализации этого базового класса или даже добавить полный исходный код здесь, но вы можете найти полный исходный код этого базового класса - я назвал его 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
, Но если вы работаете с разными типами моделей, установите для параметра типа значение 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()
тогда ни одно из ваших изменений не будет применено!
Все, что вам нужно сделать, это добавить 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;
}
});
Это пример фильтрации моей телефонной книги по имени и номеру телефона.
Следуя @Shruthi Kamoji более чистым способом, мы можем просто использовать фильтруемое, это предназначено для этого:
public abstract class GenericRecycleAdapter<E> extends RecyclerView.Adapter implements Filterable
{
protected List<E> list;
protected List<E> originalList;
protected Context context;
public GenericRecycleAdapter(Context context,
List<E> list)
{
this.originalList = list;
this.list = list;
this.context = context;
}
...
@Override
public Filter getFilter() {
return new Filter() {
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list = (List<E>) results.values;
notifyDataSetChanged();
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<E> filteredResults = null;
if (constraint.length() == 0) {
filteredResults = originalList;
} else {
filteredResults = getFilteredResults(constraint.toString().toLowerCase());
}
FilterResults results = new FilterResults();
results.values = filteredResults;
return results;
}
};
}
protected List<E> getFilteredResults(String constraint) {
List<E> results = new ArrayList<>();
for (E item : originalList) {
if (item.getName().toLowerCase().contains(constraint)) {
results.add(item);
}
}
return results;
}
}
E здесь является универсальным типом, вы можете расширить его, используя ваш класс:
public class customerAdapter extends GenericRecycleAdapter<CustomerModel>
Или просто измените E на тип, который вы хотите (<CustomerModel>
например)
Затем из searchView (виджет, который вы можете поместить в menu.xml):
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String text) {
return false;
}
@Override
public boolean onQueryTextChange(String text) {
yourAdapter.getFilter().filter(text);
return true;
}
});
В адаптере:
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;
}
});
просто создайте два списка в адаптере один оригинальный и один временный и реализует фильтруемый.
@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;
}
С помощью компонентов архитектуры Android с помощью LiveData это может быть легко реализовано с помощью любого типа адаптера. Вы просто должны сделать следующие шаги:
1. Настройте свои данные так, чтобы они возвращались из базы данных комнат как LiveData, как в примере ниже:
@Dao
public interface CustomDAO{
@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
public LiveData<List<Word>> searchFor(String searchquery);
}
2. Создайте объект ViewModel для обновления ваших данных в реальном времени с помощью метода, который соединит ваш DAO и ваш пользовательский интерфейс.
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. Вызовите данные из ViewModel на лету, передав запрос через onQueryTextListener, как показано ниже:
внутри onCreateOptionsMenu
установите слушателя следующим образом
searchView.setOnQueryTextListener(onQueryTextListener);
Настройте прослушиватель запросов где-нибудь в своем классе SearchActivity следующим образом
private android.support.v7.widget.SearchView.OnQueryTextListener onQueryTextListener =
new android.support.v7.widget.SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
getResults(query);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
getResults(newText);
return true;
}
private void getResults(String newText) {
String queryText = "%" + newText + "%";
mCustomViewModel.searchQuery(queryText).observe(
SearchResultsActivity.this, new Observer<List<Word>>() {
@Override
public void onChanged(@Nullable List<Word> words) {
if (words == null) return;
searchAdapter.submitList(words);
}
});
}
};
Примечание. Шаги (1.) и (2.) являются стандартными реализациями AAC ViewModel и DAO, единственное действительное "волшебство", происходящее здесь, заключается в OnQueryTextListener, который будет динамически обновлять результаты вашего списка при изменении текста запроса.
Если вам нужно больше разъяснений по этому вопросу, пожалуйста, не стесняйтесь спрашивать. Я надеюсь, что это помогло:).
Я не знаю, почему все используют две копии одного и того же списка для решения этой проблемы. Это использует слишком много оперативной памяти...
Почему бы просто не скрыть элементы, которые не найдены, и просто сохранить их индекс вSet
чтобы иметь возможность восстановить их позже? Это намного меньше оперативной памяти, особенно если ваши объекты довольно большие.
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.SampleViewHolders>{
private List<MyObject> myObjectsList; //holds the items of type MyObject
private Set<Integer> foundObjects; //holds the indices of the found items
public MyRecyclerViewAdapter(Context context, List<MyObject> myObjectsList)
{
this.myObjectsList = myObjectsList;
this.foundObjects = new HashSet<>();
//first, add all indices to the indices set
for(int i = 0; i < this.myObjectsList.size(); i++)
{
this.foundObjects.add(i);
}
}
@NonNull
@Override
public SampleViewHolders onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.my_layout_for_staggered_grid, null);
MyRecyclerViewAdapter.SampleViewHolders rcv = new MyRecyclerViewAdapter.SampleViewHolders(layoutView);
return rcv;
}
@Override
public void onBindViewHolder(@NonNull SampleViewHolders holder, int position)
{
//look for object in O(1) in the indices set
if(!foundObjects.contains(position))
{
//object not found => hide it.
holder.hideLayout();
return;
}
else
{
//object found => show it.
holder.showLayout();
}
//holder.imgImageView.setImageResource(...)
//holder.nameTextView.setText(...)
}
@Override
public int getItemCount() {
return myObjectsList.size();
}
public void findObject(String text)
{
//look for "text" in the objects list
for(int i = 0; i < myObjectsList.size(); i++)
{
//if it's empty text, we want all objects, so just add it to the set.
if(text.length() == 0)
{
foundObjects.add(i);
}
else
{
//otherwise check if it meets your search criteria and add it or remove it accordingly
if (myObjectsList.get(i).getName().toLowerCase().contains(text.toLowerCase()))
{
foundObjects.add(i);
}
else
{
foundObjects.remove(i);
}
}
}
notifyDataSetChanged();
}
public class SampleViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener
{
public ImageView imgImageView;
public TextView nameTextView;
private final CardView layout;
private final CardView.LayoutParams hiddenLayoutParams;
private final CardView.LayoutParams shownLayoutParams;
public SampleViewHolders(View itemView)
{
super(itemView);
itemView.setOnClickListener(this);
imgImageView = (ImageView) itemView.findViewById(R.id.some_image_view);
nameTextView = (TextView) itemView.findViewById(R.id.display_name_textview);
layout = itemView.findViewById(R.id.card_view); //card_view is the id of my androidx.cardview.widget.CardView in my xml layout
//prepare hidden layout params with height = 0, and visible layout params for later - see hideLayout() and showLayout()
hiddenLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
hiddenLayoutParams.height = 0;
shownLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public void onClick(View view)
{
//implement...
}
private void hideLayout() {
//hide the layout
layout.setLayoutParams(hiddenLayoutParams);
}
private void showLayout() {
//show the layout
layout.setLayoutParams(shownLayoutParams);
}
}
}
А у меня просто есть EditText
как мое окно поиска:
cardsSearchTextView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
myViewAdapter.findObject(editable.toString().toLowerCase());
}
});
Результат:
Я решил ту же проблему, используя ссылку с некоторыми изменениями в ней. Фильтр поиска на 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() используйте ваш адаптер. Я разложил его на фрагменты, так как мой адптер находится во фрагменте. Вы можете использовать адаптер напрямую, если он входит в ваш класс активности.
Это мой подход к расширению ответа @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++;
}
}
По сути, он просматривает полный список и добавляет / удаляет элементы в отфильтрованный список один за другим.
Добавьте интерфейс в свой адаптер.
public interface SelectedUser{
void selectedUser(UserModel userModel);
}
реализовать интерфейс в своей основной деятельности и переопределить метод. @Override public void selectedUser(UserModel userModel) {
startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));
}
Полное руководство и исходный код: Recyclerview с searchview и onclicklistener
Если вы хотите выполнять поиск по нажатию кнопки, это нормально работает.
filterIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String strCHR = homeSearchEdit.getText().toString();
if (homeSearchEdit.getText().toString().length() > 0) {
ArrayList<ServiceModel> listNew = new ArrayList<>();
for (int l = 0; l < arrayList.size(); l++) {
String serviceName = arrayList.get(l).getServiceName().toLowerCase();
if (serviceName.contains(strCHR.toLowerCase())) {
listNew.add(arrayList.get(l));
}
}
recyclerView.setVisibility(View.VISIBLE);
adapter = new ServiceAdapter(HomeActivity.this, listNew);
recyclerView.setAdapter(adapter);
} else {
recyclerView.setVisibility(View.VISIBLE);
adapter = new ServiceAdapter(HomeActivity.this, arrayList);
recyclerView.setAdapter(adapter);
}
}
});
где , filterIcon кнопка и homeSearchEdit является EditText(где мы применяем для поиска).
Я рекомендую изменить решение @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);
}
}
Надеюсь, что это может помочь вам!
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 в отфильтрованный список.
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 Class Setup ваш адаптер и ваш фильтр
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 Setup ваш адаптер и ваш фильтр
private fun setupAdapter() {
adapter = CardAdapter(mItems)
recyclerView.adapter = adapter
}
fun filter(searchString : String){
adapter.filter.filter(searchString)
}