Как фильтровать 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);
}
}
}
1021   7  

7 ответов:

введение

поскольку не совсем ясно, с чем именно у вас возникли проблемы, я написал это краткое пошаговое руководство о том, как реализовать эту функцию, если у вас все еще есть вопросы, не стесняйтесь спрашивать.

у меня есть рабочий пример всего, о чем я говорю здесь в этом Репозиторий GitHub.
Если вы хотите узнать больше о пример проекта посетите Домашняя страница проекта.

в любом случае результат должен выглядеть примерно так:

demo image

если вы сначала хотите поиграть с демо-приложением, вы можете установить его из Play Store:

Get it on Google Play

в любом случае, давайте начнем.


настройка SearchView

в папке res/menu создать новый файл называется main_menu.xml. В нем добавьте элемент и установите actionViewClass до 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

The SortedList - это совершенно удивительный инструмент который является частью RecyclerView библиотека. Он заботится о уведомлении Adapter об изменениях в наборе данных и делает это очень эффективным способом. Единственное, что вам нужно сделать, это указать порядок элементов. Вам нужно сделать это, реализовав compare() метод, который сравнивает два элемента в SortedList как Comparator. Но вместо сортировки List используется для сортировки элементов в RecyclerView!

The 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 и т. д. вы должны вызвать эквивалентный метод 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);
}

цель этого метода-определить, изменилось ли содержимое модели. Элемент 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();
}

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

The 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 на 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 на ExampleModels, а также строка запроса. Затем мы вызываем 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 мы прошли в нее и проверяет, если строка запроса содержится в текст модели. Если это так, то модель добавляется к фильтруемому List.

и это все! Приведенный выше код будет работать на уровне API 7 и выше, и начиная с уровня API 11 вы получаете анимацию элементов бесплатно!

я понимаю, что это очень подробное описание, которое, вероятно, делает все это более сложным, чем на самом деле, но есть способ обобщить всю эту проблему и сделать реализацию Adapter на основе SortedList гораздо проще.


обобщение проблемы и упрощение адаптера

в этом разделе я не буду вдаваться в подробности-отчасти потому, что я сталкиваюсь с ограничением символов для ответов на переполнение стека, но также и потому, что большинство из них уже объяснено выше, но чтобы суммировать изменения: мы можем реализовать базу Adapter класс, который уже заботится о решении SortedList а также привязка моделей к ViewHolder экземпляров и обеспечивает удобный способ реализации Adapter на основе SortedList. Для этого мы должны сделать две вещи:

  • нам нужно создать ViewModel интерфейс, который все классы моделей должны реализовать
  • нам нужно создать ViewHolder подкласс, который определяет a 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

использовать элемент 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();

все изменения, которые вы делаете таким образом, объединяются вместе для повышения производительности. Элемент 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;
    }
});

это пример фильтрации моей телефонной книги по имени и номеру телефона.

после @начала знакомства с Ченнай 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 (виджет вы можете поместить в меню.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 с двумя вещами ниже, чтобы избежать проблемы после того, как вы очистили искомый текст (фильтр больше не работал) из-за того, что список back of adapter имеет меньший размер, чем список фильтров, и произошло исключение 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;
            }
        });

Comments

    Ничего не найдено.