Как фильтровать 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.
Если вы хотите узнать больше о пример проекта посетите Домашняя страница проекта.

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

demo image

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

Get it on Google Play

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


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