Как фильтровать RecyclerView с помощью SearchView
Я пытаюсь реализовать SearchView
из библиотеки поддержки. Я хочу, чтобы пользователь использовал SearchView
для фильтрации a 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);
}
}
}
7 ответов
введение
так как это не совсем ясно, форма вашего вопроса с тем, что именно у вас возникли проблемы, я написал это краткое пошаговое руководство о том, как реализовать эту функцию, если у вас все еще есть вопросы, не стесняйтесь спрашивать.
у меня есть рабочий пример всего, о чем я говорю здесь в этом Репозиторий GitHub.
Если вы хотите узнать больше о пример проекта посетите Домашняя страница проекта.
в любом случае результат должен выглядеть примерно так:
если вы сначала хотите поиграть с демонстрационным приложением, вы можете установить его из Play Store:
в любом случае, давайте начнем.
настройка SearchView
в папке res/menu
создать новый файл называется main_menu.xml
. В нем добавьте элемент и установите actionViewClass
to android.support.v7.widget.SearchView
. Поскольку вы используете библиотеку поддержки, вы должны использовать пространство имен библиотеки поддержки для установки . Ваш 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) MenuItemCompat.getActionView(searchItem);
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
. Это макет I собираюсь использовать для отображения текста:
<?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
, etc. вы должны вызвать эквивалентный метод notify вашего 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);
}
цель этого метода-определить, изменилось ли содержимое модели. The 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
works) это используется для определения, если объект уже содержится в List
и если добавить, перемещение или изменение анимации должны быть воспроизведены. Если ваши модели имеют идентификатор, вы обычно сравниваете только идентификатор в этом методе. Если они не вам нужно выяснить какой-то другой способ проверить это, но однако вы в конечном итоге реализуете это зависит от вашего конкретного приложения. Обычно это самый простой вариант дать всем моделям 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()
the 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
of 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
of ExampleModel
s, а также строка запроса. Затем мы вызываем replaceAll()
на Adapter
и пройти в отфильтрованный List
возвращено filter()
. Мы также должны позвонить scrollToPosition(0)
на RecyclerView
чтобы пользователь всегда мог видеть все элементы при поиске чего-либо. В противном случае RecyclerView
может оставаться в прокрученном вниз положении во время фильтрации и впоследствии скрыть несколько элементов. Прокрутка вверх обеспечивает лучший пользовательский интерфейс, в то время как испытующий.
единственное, что осталось сделать сейчас, чтобы реализовать :
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
мы прошли в него и проверяем, содержится ли строка запроса в текст модели. Если это так, то модель добавляется в filtered List
.
и это все! Приведенный выше код будет работать на уровне API 7 и выше и начиная с уровня API 11 вы получаете анимацию элемента бесплатно!
я понимаю, что это очень подробное описание, которое, вероятно, делает все это более сложным, чем на самом деле, но есть способ обобщить всю эту проблему и сделать реализацию Adapter
на основе SortedList
гораздо проще.
обобщение проблемы и упрощение адаптера
в этом разделе я не собираюсь вдаваться в подробности-отчасти потому, что я сталкиваюсь с ограничением символов для ответов на переполнение стека, но и потому, что большинство из них уже объяснено выше , но суммировать изменения: мы можем реализовать базу Adapter
класс, который уже заботится о решении SortedList
а также привязка моделей к ViewHolder
экземпляров и обеспечивает удобный способ реализации Adapter
на основе SortedList
. Для этого мы должны сделать две вещи:--180-->
- нам нужно создать
ViewModel
интерфейс, который должны реализовать все классы моделей - нам нужно создать
ViewHolder
подкласс, который определяетbind()
методAdapter
можно использовать для автоматической привязки моделей.
это позволяет нам просто сосредоточиться на содержимом, которое должно отображаться в RecyclerView
by просто реализуем модели и там соответствующие ViewHolder
реализаций. Используя этот базовый класс, нам не нужно беспокоиться о сложных деталях Adapter
и SortedList
.
SortedListAdapter
из-за ограничения символов для ответов на StackOverflow я не могу пройти каждый шаг реализации этого базового класса или даже добавить полный исходный код здесь, но вы можете найти полный исходный код этого базового класса - я назвал его SortedListAdapter
- in это GitHub Gist.
чтобы сделать вашу жизнь простой, я опубликовал библиотеку на jCenter, которая содержит SortedListAdapter
! Если вы хотите использовать его, все, что вам нужно сделать, это добавить эту зависимость в сборку вашего приложения.файл gradle:
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
вы можете найти более подробную информацию о этой библиотеке на главной странице библиотеки.
использование SortedListAdapter
использовать the SortedListAdapter
мы должны внести два изменения:
-
изменить
ViewHolder
так что она расширяетсяSortedListAdapter.ViewHolder
. Параметр type должен быть моделью, которая должна быть привязана к этому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();
все изменения, которые вы делаете таким образом, группируются вместе, чтобы увеличить производительность. The replaceAll()
метод, который мы реализовали в главах выше также присутствует на этом :
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 более чистым способом, мы можем просто использовать 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 (виджет вы можете поместить в меню.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;
}
});
просто создайте два списка в адаптере один orignal и один temp и реализует Filterable.
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
final FilterResults oReturn = new FilterResults();
final ArrayList<T> results = new ArrayList<>();
if (origList == null)
origList = new ArrayList<>(itemList);
if (constraint != null && constraint.length() > 0) {
if (origList != null && origList.size() > 0) {
for (final T cd : origList) {
if (cd.getAttributeToSearch().toLowerCase()
.contains(constraint.toString().toLowerCase()))
results.add(cd);
}
}
oReturn.values = results;
oReturn.count = results.size();//newly Aded by ZA
} else {
oReturn.values = origList;
oReturn.count = origList.size();//newly added by ZA
}
return oReturn;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(final CharSequence constraint,
FilterResults results) {
itemList = new ArrayList<>((ArrayList<T>) results.values);
// FIXME: 8/16/2017 implement Comparable with sort below
///Collections.sort(itemList);
notifyDataSetChanged();
}
};
}
здесь
public GenericBaseAdapter(Context mContext, List<T> itemList) {
this.mContext = mContext;
this.itemList = itemList;
this.origList = itemList;
}
Я рекомендую изменить решение @Xaver Kapeller с 2 вещами ниже, чтобы избежать проблемы после того, как вы очистили искомый текст (фильтр больше не работал) из-за того, что список назад адаптера имеет меньший размер, чем список фильтров, и произошло исключение 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);
}
}
надеюсь, что это может помочь вам!
я решил ту же проблему, используя ссылку с некоторыми изменениями в ней. фильтр поиска на 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 () используйте адаптер. Я разбил его на части, как разбил мой адптер. Можно использовать адаптер непосредственно, если это в вашем классе активности.
Блок Питания:
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;
}
});