Как отфильтровать RecyclerView с помощью SearchView

Я пытаюсь реализовать SearchView из библиотеки поддержки. Я хочу, чтобы пользователь использовал SearchView фильтровать List фильмов в RecyclerView,

До сих пор я следовал нескольким учебникам и добавил SearchView к ActionBar, но я не совсем уверен, куда идти отсюда. Я видел несколько примеров, но ни один из них не показывает результаты, когда вы начинаете печатать.

Это мое MainActivity:

public class MainActivity extends ActionBarActivity {

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

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

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

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

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

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

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

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

        return super.onOptionsItemSelected(item);
    }
}

А это мой Adapter:

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

    List<Movie> mItems;

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

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

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

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

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

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

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

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

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

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

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

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

    class ViewHolder extends RecyclerView.ViewHolder{

        public TextView tvMovie;
        public TextView tvMovieRating;

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

13 ответов

Решение

Вступление

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

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

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

демонстрационное изображение

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

Получить его в Google Play

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


Настройка SearchView

В папке res/menu создать новый файл с именем main_menu.xml, В нем добавить элемент и установить actionViewClass в android.support.v7.widget.SearchView, Поскольку вы используете библиотеку поддержки, вы должны использовать пространство имен библиотеки поддержки, чтобы установить actionViewClass приписывать. Ваш XML-файл должен выглядеть примерно так:

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

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

</menu>

В вашем Fragment или же Activity Вы должны надуть это меню XML как обычно, тогда вы можете посмотреть на MenuItem который содержит SearchView и реализовать OnQueryTextListener который мы будем использовать для прослушивания изменений в тексте, введенном в SearchView:

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

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

    return true;
}

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

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

А теперь SearchView готов к использованию. Мы реализуем логику фильтра позже в onQueryTextChange() как только мы закончим реализацию Adapter,


Настройка Adapter

Прежде всего, это класс модели, который я собираюсь использовать для этого примера:

public class ExampleModel {

    private final long mId;
    private final String mText;

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

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }
}

Это просто ваша базовая модель, которая будет отображать текст в RecyclerView, Это макет, который я собираюсь использовать для отображения текста:

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

    <data>

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

    </data>

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

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

    </FrameLayout>

</layout>

Как видите, я использую привязку данных. Если вы никогда не работали с привязкой данных, не расстраивайтесь! Это очень просто и мощно, однако я не могу объяснить, как это работает в рамках этого ответа.

Это ViewHolder для ExampleModel учебный класс:

public class ExampleViewHolder extends RecyclerView.ViewHolder {

    private final ItemExampleBinding mBinding;

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

    public void bind(ExampleModel item) {
        mBinding.setModel(item);
    }
}

Опять ничего особенного. Он просто использует привязку данных для привязки класса модели к этому макету, как мы определили в макете XML выше.

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

Но сначала нам нужно поговорить об одном: SortedList учебный класс.


SortedList

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

SortedList взаимодействует с Adapter через Callback класс, который вы должны реализовать:

private final SortedList.Callback<ExampleModel> mCallback = new SortedList.Callback<ExampleModel>() {

    @Override
    public void onInserted(int position, int count) {
         mAdapter.notifyItemRangeInserted(position, count);
    }

    @Override
    public void onRemoved(int position, int count) {
        mAdapter.notifyItemRangeRemoved(position, count);
    }

    @Override
    public void onMoved(int fromPosition, int toPosition) {
        mAdapter.notifyItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onChanged(int position, int count) {
        mAdapter.notifyItemRangeChanged(position, count);
    }

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

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

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

В методах в верхней части обратного вызова, как onMoved, onInserted и т. д. вы должны вызвать эквивалентный метод уведомления вашего Adapter, Три метода внизу compare, areContentsTheSame а также areItemsTheSame Вы должны реализовать в соответствии с тем, какие объекты вы хотите отобразить и в каком порядке эти объекты должны отображаться на экране.

Давайте рассмотрим эти методы один за другим:

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

Это compare() метод, о котором я говорил ранее. В этом примере я просто передаю вызов Comparator который сравнивает две модели. Если вы хотите, чтобы элементы отображались на экране в алфавитном порядке. Этот компаратор может выглядеть так:

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

Теперь давайте посмотрим на следующий метод:

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

Цель этого метода - определить, изменилось ли содержание модели. SortedList использует это, чтобы определить, нужно ли вызывать событие изменения - другими словами, если RecyclerView следует поменять местами старую и новую версию. Если у вас модели классов есть правильные equals() а также hashCode() Реализацию вы можете просто реализовать, как описано выше. Если мы добавим equals() а также hashCode() реализация к ExampleModel Класс должен выглядеть примерно так:

public class ExampleModel implements SortedListAdapter.ViewModel {

    private final long mId;
    private final String mText;

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

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }

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

        ExampleModel model = (ExampleModel) o;

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

    }

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

Краткое примечание: большинство IDE, таких как Android Studio, IntelliJ и Eclipse, имеют функции для генерации equals() а также hashCode() реализации для вас одним нажатием кнопки! Так что вам не нужно реализовывать их самостоятельно. Посмотрите в Интернете, как это работает в вашей IDE!

Теперь давайте посмотрим на последний метод:

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

SortedList использует этот метод, чтобы проверить, относятся ли два элемента к одной и той же вещи. Проще говоря (без объяснения того, как SortedList работает) это используется, чтобы определить, содержится ли объект в List и если необходимо воспроизвести анимацию добавления, перемещения или изменения. Если ваши модели имеют идентификатор, вы обычно сравниваете только идентификатор в этом методе. Если это не так, вам нужно найти какой-то другой способ проверить это, но, тем не менее, вы в конечном итоге реализуете это, зависит от вашего конкретного приложения. Обычно это самый простой способ присвоить идентификаторы всем моделям - например, это может быть поле первичного ключа, если вы запрашиваете данные из базы данных.

С SortedList.Callback правильно реализовано, мы можем создать экземпляр SortedList:

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

В качестве первого параметра в конструкторе SortedList вам нужно пройти класс ваших моделей. Другой параметр - это просто SortedList.Callback мы определили выше.

Теперь давайте приступим к делу: если мы реализуем Adapter с SortedList это должно выглядеть примерно так:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

    private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
        @Override
        public int compare(ExampleModel a, ExampleModel b) {
            return mComparator.compare(a, b);
        }

        @Override
        public void onInserted(int position, int count) {
            notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count) {
            notifyItemRangeChanged(position, count);
        }

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

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

    private final LayoutInflater mInflater;
    private final Comparator<ExampleModel> mComparator;

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
        mInflater = LayoutInflater.from(context);
        mComparator = comparator;
    }

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
        return new ExampleViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) {
        final ExampleModel model = mSortedList.get(position);
        holder.bind(model);
    }

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

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

Теперь мы почти закончили! Но сначала нам нужен способ добавить или удалить элементы в Adapter, Для этого мы можем добавить методы Adapter которые позволяют нам добавлять и удалять элементы в SortedList:

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

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

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

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

Нам не нужно вызывать какие-либо методы уведомления здесь, потому что SortedList уже делает это через SortedList.Callback! Кроме того, реализация этих методов довольно проста, за одним исключением: метод remove, который удаляет List моделей. Так как SortedList есть только один метод удаления, который может удалить один объект, который нам необходим для циклического перемещения по списку и удаления моделей по одной. призвание beginBatchedUpdates() в начале партии все изменения, которые мы собираемся внести в SortedList вместе и улучшает производительность. Когда мы звоним endBatchedUpdates() RecyclerView уведомляется обо всех изменениях сразу.

Кроме того, вы должны понимать, что если вы добавляете объект в SortedList и это уже в SortedList это не будет добавлено снова. Вместо SortedList использует areContentsTheSame() метод, чтобы выяснить, изменился ли объект - и если у него есть элемент в RecyclerView будет обновлено.

В любом случае, я обычно предпочитаю один метод, который позволяет мне заменить все элементы в RecyclerView однажды. Удалить все, чего нет в List и добавить все элементы, которые отсутствуют в SortedList:

public void replaceAll(List<ExampleModel> models) {
    mSortedList.beginBatchedUpdates();
    for (int i = mSortedList.size() - 1; i >= 0; i--) {
        final ExampleModel model = mSortedList.get(i);
        if (!models.contains(model)) {
            mSortedList.remove(model);
        }
    }
    mSortedList.addAll(models);
    mSortedList.endBatchedUpdates();
}

Этот метод снова объединяет все обновления для повышения производительности. Первый цикл является обратным, поскольку удаление элемента в начале может испортить индексы всех элементов, которые появляются после него, и в некоторых случаях это может привести к таким проблемам, как несоответствия данных. После этого мы просто добавляем List к SortedList с помощью addAll() добавить все элементы, которых еще нет в SortedList и - так же, как я описал выше - обновить все элементы, которые уже находятся в SortedList но изменились.

И с этим Adapter завершено. Все это должно выглядеть примерно так:

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

    private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
        @Override
        public int compare(ExampleModel a, ExampleModel b) {
            return mComparator.compare(a, b);
        }

        @Override
        public void onInserted(int position, int count) {
            notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count) {
            notifyItemRangeChanged(position, count);
        }

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

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

    private final Comparator<ExampleModel> mComparator;
    private final LayoutInflater mInflater;

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
        mInflater = LayoutInflater.from(context);
        mComparator = comparator;
    }

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final ItemExampleBinding binding = ItemExampleBinding.inflate(mInflater, parent, false);
        return new ExampleViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) {
        final ExampleModel model = mSortedList.get(position);
        holder.bind(model);
    }

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

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

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

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

    public void replaceAll(List<ExampleModel> models) {
        mSortedList.beginBatchedUpdates();
        for (int i = mSortedList.size() - 1; i >= 0; i--) {
            final ExampleModel model = mSortedList.get(i);
            if (!models.contains(model)) {
                mSortedList.remove(model);
            }
        }
        mSortedList.addAll(models);
        mSortedList.endBatchedUpdates();
    }

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

Единственное, чего не хватает сейчас - это реализовать фильтрацию!


Реализация логики фильтра

Для реализации логики фильтра мы должны сначала определить List из всех возможных моделей. Для этого примера я создаю List из ExampleModel экземпляры из массива фильмов:

private static final String[] MOVIES = new String[]{
        ...
};

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

private ExampleAdapter mAdapter;
private List<ExampleModel> mModels;
private RecyclerView mRecyclerView;

    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    mAdapter = new ExampleAdapter(this, ALPHABETICAL_COMPARATOR);

    mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
    mBinding.recyclerView.setAdapter(mAdapter);

    mModels = new ArrayList<>();
    for (String movie : MOVIES) {
        mModels.add(new ExampleModel(movie));
    }
    mAdapter.add(mModels);
}

Ничего особенного здесь не происходит, мы просто создаем Adapter и установите его на RecyclerView, После этого мы создаем List моделей из названий фильмов в MOVIES массив. Затем мы добавляем все модели в SortedList,

Теперь мы можем вернуться к onQueryTextChange() который мы определили ранее и приступим к реализации логики фильтра:

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

Это снова довольно просто. Мы называем метод filter() и пройти в List из ExampleModel s, а также строка запроса. Затем мы позвоним replaceAll() на Adapter и передать в отфильтрованном List вернулся filter(), Мы также должны позвонить scrollToPosition(0) на RecyclerView чтобы пользователь всегда мог видеть все элементы при поиске чего-либо. В противном случае RecyclerView во время фильтрации может оставаться в положении прокрутки вниз и впоследствии скрывать несколько элементов. Прокрутка вверх обеспечивает лучшее взаимодействие с пользователем при поиске.

Единственное, что осталось сделать сейчас, это реализовать filter() сам:

private static List<ExampleModel> filter(List<ExampleModel> models, String query) {
    final String lowerCaseQuery = query.toLowerCase();

    final List<ExampleModel> filteredModelList = new ArrayList<>();
    for (ExampleModel model : models) {
        final String text = model.getText().toLowerCase();
        if (text.contains(lowerCaseQuery)) {
            filteredModelList.add(model);
        }
    }
    return filteredModelList;
}

Первое, что мы делаем здесь, это звоните toLowerCase() в строке запроса. Мы не хотим, чтобы наша функция поиска учитывала регистр и вызывая toLowerCase() на всех сравниваемых строках мы можем гарантировать, что мы возвращаем одинаковые результаты независимо от регистра. Затем он просто перебирает все модели в List мы прошли в него и проверили, содержится ли строка запроса в тексте модели. Если это так, то модель добавляется в фильтруемый List,

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

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


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

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

  • Нам нужно создать ViewModel интерфейс, который должны реализовывать все классы моделей
  • Нам нужно создать ViewHolder подкласс, который определяет bind() метод Adapter можно использовать для автоматического связывания моделей.

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

SortedListAdapter

Из-за ограничения символов для ответов в Stack Overflow я не могу пройти каждый шаг реализации этого базового класса или даже добавить полный исходный код здесь, но вы можете найти полный исходный код этого базового класса - я назвал его SortedListAdapter - в этом GitHub Gist.

Чтобы сделать вашу жизнь проще, я опубликовал библиотеку на jCenter, которая содержит SortedListAdapter! Если вы хотите использовать его, все, что вам нужно сделать, это добавить эту зависимость в файл build.gradle вашего приложения:

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

Вы можете найти больше информации об этой библиотеке на домашней странице библиотеки.

Использование SortedListAdapter

Чтобы использовать SortedListAdapter мы должны сделать два изменения:

  • Изменить ViewHolder так что он расширяется SortedListAdapter.ViewHolder, Параметр типа должен быть моделью, которая должна быть связана с этим ViewHolder - в этом случае ExampleModel, Вы должны привязать данные к своим моделям в performBind() вместо bind(),

    public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> {
    
        private final ItemExampleBinding mBinding;
    
        public ExampleViewHolder(ItemExampleBinding binding) {
            super(binding.getRoot());
            mBinding = binding;
        }
    
        @Override
        protected void performBind(ExampleModel item) {
            mBinding.setModel(item);
        }
    }
    
  • Убедитесь, что все ваши модели реализуют ViewModel интерфейс:

    public class ExampleModel implements SortedListAdapter.ViewModel {
        ...
    }
    

После этого нам просто нужно обновить ExampleAdapter расширить SortedListAdapter и удалите все, что нам больше не нужно. Параметр type должен соответствовать типу модели, с которой вы работаете - в этом случае ExampleModel, Но если вы работаете с разными типами моделей, установите для параметра типа значение ViewModel,

public class ExampleAdapter extends SortedListAdapter<ExampleModel> {

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
        super(context, ExampleModel.class, comparator);
    }

    @Override
    protected ViewHolder<? extends ExampleModel> onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) {
        final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
        return new ExampleViewHolder(binding);
    }

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

    @Override
    protected boolean areItemContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
        return oldItem.equals(newItem);
    }
}

После этого мы сделали! Однако еще одна вещь, которую стоит упомянуть: SortedListAdapter не имеет того же add(), remove() или же replaceAll() методы наши оригинальные ExampleAdapter имел. Он использует отдельный Editor объект для изменения элементов в списке, к которым можно получить доступ через edit() метод. Поэтому, если вы хотите удалить или добавить элементы, вам нужно позвонить edit() затем добавьте и удалите элементы на этом Editor экземпляр и как только вы закончите, позвоните commit() на нем, чтобы применить изменения к SortedList:

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

Все изменения, которые вы делаете таким образом, объединяются для повышения производительности. replaceAll() метод, который мы реализовали в главах выше, также присутствует на этом Editor объект:

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

Если вы забудете позвонить commit() тогда ни одно из ваших изменений не будет применено!

Все, что вам нужно сделать, это добавить filter метод в RecyclerView.Adapter:

public void filter(String text) {
    items.clear();
    if(text.isEmpty()){
        items.addAll(itemsCopy);
    } else{
        text = text.toLowerCase();
        for(PhoneBookItem item: itemsCopy){
            if(item.name.toLowerCase().contains(text) || item.phone.toLowerCase().contains(text)){
                items.add(item);
            }
        }
    }
    notifyDataSetChanged();
}

itemsCopy инициализируется в конструкторе адаптера, как itemsCopy.addAll(items),

Если вы это сделаете, просто позвоните filter от OnQueryTextListener:

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    @Override
    public boolean onQueryTextSubmit(String query) {
        adapter.filter(query);
        return true;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        adapter.filter(newText);
        return true;
    }
});

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

Следуя @Shruthi Kamoji более чистым способом, мы можем просто использовать фильтруемое, это предназначено для этого:

public abstract class GenericRecycleAdapter<E> extends RecyclerView.Adapter implements Filterable
{
    protected List<E> list;
    protected List<E> originalList;
    protected Context context;

    public GenericRecycleAdapter(Context context,
    List<E> list)
    {
        this.originalList = list;
        this.list = list;
        this.context = context;
    }

    ...

    @Override
    public Filter getFilter() {
        return new Filter() {
            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                list = (List<E>) results.values;
                notifyDataSetChanged();
            }

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                List<E> filteredResults = null;
                if (constraint.length() == 0) {
                    filteredResults = originalList;
                } else {
                    filteredResults = getFilteredResults(constraint.toString().toLowerCase());
                }

                FilterResults results = new FilterResults();
                results.values = filteredResults;

                return results;
            }
        };
    }

    protected List<E> getFilteredResults(String constraint) {
        List<E> results = new ArrayList<>();

        for (E item : originalList) {
            if (item.getName().toLowerCase().contains(constraint)) {
                results.add(item);
            }
        }
        return results;
    }
} 

E здесь является универсальным типом, вы можете расширить его, используя ваш класс:

public class customerAdapter extends GenericRecycleAdapter<CustomerModel>

Или просто измените E на тип, который вы хотите (<CustomerModel> например)

Затем из searchView (виджет, который вы можете поместить в menu.xml):

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    @Override
    public boolean onQueryTextSubmit(String text) {
        return false;
    }

    @Override
    public boolean onQueryTextChange(String text) {
        yourAdapter.getFilter().filter(text);
        return true;
    }
});

В адаптере:

public void setFilter(List<Channel> newList){
        mChannels = new ArrayList<>();
        mChannels.addAll(newList);
        notifyDataSetChanged();
    }

В деятельности:

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                newText = newText.toLowerCase();
                ArrayList<Channel> newList = new ArrayList<>();
                for (Channel channel: channels){
                    String channelName = channel.getmChannelName().toLowerCase();
                    if (channelName.contains(newText)){
                        newList.add(channel);
                    }
                }
                mAdapter.setFilter(newList);
                return true;
            }
        });

просто создайте два списка в адаптере один оригинальный и один временный и реализует фильтруемый.

    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                final FilterResults oReturn = new FilterResults();
                final ArrayList<T> results = new ArrayList<>();
                if (origList == null)
                    origList = new ArrayList<>(itemList);
                if (constraint != null && constraint.length() > 0) {
                    if (origList != null && origList.size() > 0) {
                        for (final T cd : origList) {
                            if (cd.getAttributeToSearch().toLowerCase()
                                    .contains(constraint.toString().toLowerCase()))
                                results.add(cd);
                        }
                    }
                    oReturn.values = results;
                    oReturn.count = results.size();//newly Aded by ZA
                } else {
                    oReturn.values = origList;
                    oReturn.count = origList.size();//newly added by ZA
                }
                return oReturn;
            }

            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(final CharSequence constraint,
                                          FilterResults results) {
                itemList = new ArrayList<>((ArrayList<T>) results.values);
                // FIXME: 8/16/2017 implement Comparable with sort below
                ///Collections.sort(itemList);
                notifyDataSetChanged();
            }
        };
    }

где

public GenericBaseAdapter(Context mContext, List<T> itemList) {
        this.mContext = mContext;
        this.itemList = itemList;
        this.origList = itemList;
    }

С помощью компонентов архитектуры Android с помощью LiveData это может быть легко реализовано с помощью любого типа адаптера. Вы просто должны сделать следующие шаги:

1. Настройте свои данные так, чтобы они возвращались из базы данных комнат как LiveData, как в примере ниже:

@Dao
public interface CustomDAO{

@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
    public LiveData<List<Word>> searchFor(String searchquery);
}

2. Создайте объект ViewModel для обновления ваших данных в реальном времени с помощью метода, который соединит ваш DAO и ваш пользовательский интерфейс.

public class CustomViewModel extends AndroidViewModel {

    private final AppDatabase mAppDatabase;

    public WordListViewModel(@NonNull Application application) {
        super(application);
        this.mAppDatabase = AppDatabase.getInstance(application.getApplicationContext());
    }

    public LiveData<List<Word>> searchQuery(String query) {
        return mAppDatabase.mWordDAO().searchFor(query);
    }

}

3. Вызовите данные из ViewModel на лету, передав запрос через onQueryTextListener, как показано ниже:

внутри onCreateOptionsMenu установите слушателя следующим образом

searchView.setOnQueryTextListener(onQueryTextListener);

Настройте прослушиватель запросов где-нибудь в своем классе SearchActivity следующим образом

private android.support.v7.widget.SearchView.OnQueryTextListener onQueryTextListener =
            new android.support.v7.widget.SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String query) {
                    getResults(query);
                    return true;
                }

                @Override
                public boolean onQueryTextChange(String newText) {
                    getResults(newText);
                    return true;
                }

                private void getResults(String newText) {
                    String queryText = "%" + newText + "%";
                    mCustomViewModel.searchQuery(queryText).observe(
                            SearchResultsActivity.this, new Observer<List<Word>>() {
                                @Override
                                public void onChanged(@Nullable List<Word> words) {
                                    if (words == null) return;
                                    searchAdapter.submitList(words);
                                }
                            });
                }
            };

Примечание. Шаги (1.) и (2.) являются стандартными реализациями AAC ViewModel и DAO, единственное действительное "волшебство", происходящее здесь, заключается в OnQueryTextListener, который будет динамически обновлять результаты вашего списка при изменении текста запроса.

Если вам нужно больше разъяснений по этому вопросу, пожалуйста, не стесняйтесь спрашивать. Я надеюсь, что это помогло:).

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

Почему бы просто не скрыть элементы, которые не найдены, и просто сохранить их индекс вSetчтобы иметь возможность восстановить их позже? Это намного меньше оперативной памяти, особенно если ваши объекты довольно большие.

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.SampleViewHolders>{
    private List<MyObject> myObjectsList; //holds the items of type MyObject
    private Set<Integer> foundObjects; //holds the indices of the found items

    public MyRecyclerViewAdapter(Context context, List<MyObject> myObjectsList)
    {
        this.myObjectsList = myObjectsList;
        this.foundObjects = new HashSet<>();
        //first, add all indices to the indices set
        for(int i = 0; i < this.myObjectsList.size(); i++)
        {
            this.foundObjects.add(i);
        }
    }

    @NonNull
    @Override
    public SampleViewHolders onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View layoutView = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.my_layout_for_staggered_grid, null);
        MyRecyclerViewAdapter.SampleViewHolders rcv = new MyRecyclerViewAdapter.SampleViewHolders(layoutView);
        return rcv;
    }

    @Override
    public void onBindViewHolder(@NonNull SampleViewHolders holder, int position)
    {
        //look for object in O(1) in the indices set
        if(!foundObjects.contains(position))
        {
            //object not found => hide it.
            holder.hideLayout();
            return;
        }
        else
        {
            //object found => show it.
            holder.showLayout();
        }

        //holder.imgImageView.setImageResource(...)
        //holder.nameTextView.setText(...)
    }

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

    public void findObject(String text)
    {
        //look for "text" in the objects list
        for(int i = 0; i < myObjectsList.size(); i++)
        {
            //if it's empty text, we want all objects, so just add it to the set.
            if(text.length() == 0)
            {
                foundObjects.add(i);
            }
            else
            {
                //otherwise check if it meets your search criteria and add it or remove it accordingly
                if (myObjectsList.get(i).getName().toLowerCase().contains(text.toLowerCase()))
                {
                    foundObjects.add(i);
                }
                else
                {
                    foundObjects.remove(i);
                }
            }
        }
        notifyDataSetChanged();
    }

    public class SampleViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener
    {
        public ImageView imgImageView;
        public TextView nameTextView;

        private final CardView layout;
        private final CardView.LayoutParams hiddenLayoutParams;
        private final CardView.LayoutParams shownLayoutParams;

        
        public SampleViewHolders(View itemView)
        {
            super(itemView);
            itemView.setOnClickListener(this);
            imgImageView = (ImageView) itemView.findViewById(R.id.some_image_view);
            nameTextView = (TextView) itemView.findViewById(R.id.display_name_textview);

            layout = itemView.findViewById(R.id.card_view); //card_view is the id of my androidx.cardview.widget.CardView in my xml layout
            //prepare hidden layout params with height = 0, and visible layout params for later - see hideLayout() and showLayout()
            hiddenLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            hiddenLayoutParams.height = 0;
            shownLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }

        @Override
        public void onClick(View view)
        {
            //implement...
        }

        private void hideLayout() {
            //hide the layout
            layout.setLayoutParams(hiddenLayoutParams);
        }

        private void showLayout() {
            //show the layout
            layout.setLayoutParams(shownLayoutParams);
        }
    }
}

А у меня просто есть EditText как мое окно поиска:

cardsSearchTextView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                myViewAdapter.findObject(editable.toString().toLowerCase());
            }
        });

Результат:

Я решил ту же проблему, используя ссылку с некоторыми изменениями в ней. Фильтр поиска на RecyclerView с картами. Это вообще возможно? (надеюсь это поможет).

Вот мой класс адаптера

public class ContactListRecyclerAdapter extends RecyclerView.Adapter<ContactListRecyclerAdapter.ContactViewHolder> implements Filterable {

Context mContext;
ArrayList<Contact> customerList;
ArrayList<Contact> parentCustomerList;


public ContactListRecyclerAdapter(Context context,ArrayList<Contact> customerList)
{
    this.mContext=context;
    this.customerList=customerList;
    if(customerList!=null)
    parentCustomerList=new ArrayList<>(customerList);
}

   // other overrided methods

@Override
public Filter getFilter() {
    return new FilterCustomerSearch(this,parentCustomerList);
}
}

// Класс фильтра

import android.widget.Filter;
import java.util.ArrayList;


public class FilterCustomerSearch extends Filter
{
private final ContactListRecyclerAdapter mAdapter;
ArrayList<Contact> contactList;
ArrayList<Contact> filteredList;

public FilterCustomerSearch(ContactListRecyclerAdapter mAdapter,ArrayList<Contact> contactList) {
    this.mAdapter = mAdapter;
    this.contactList=contactList;
    filteredList=new ArrayList<>();
}

@Override
protected FilterResults performFiltering(CharSequence constraint) {
    filteredList.clear();
    final FilterResults results = new FilterResults();

    if (constraint.length() == 0) {
        filteredList.addAll(contactList);
    } else {
        final String filterPattern = constraint.toString().toLowerCase().trim();

        for (final Contact contact : contactList) {
            if (contact.customerName.contains(constraint)) {
                filteredList.add(contact);
            }
            else if (contact.emailId.contains(constraint))
            {
                filteredList.add(contact);

            }
            else if(contact.phoneNumber.contains(constraint))
                filteredList.add(contact);
        }
    }
    results.values = filteredList;
    results.count = filteredList.size();
    return results;
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
    mAdapter.customerList.clear();
    mAdapter.customerList.addAll((ArrayList<Contact>) results.values);
    mAdapter.notifyDataSetChanged();
}

}

// Класс деятельности

public class HomeCrossFadeActivity extends AppCompatActivity implements View.OnClickListener,OnFragmentInteractionListener,OnTaskCompletedListner
{
Fragment fragment;
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_homecrossfadeslidingpane2);CardView mCard;
   setContentView(R.layout.your_main_xml);}
   //other overrided methods
  @Override
   public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.

    MenuInflater inflater = getMenuInflater();
    // Inflate menu to add items to action bar if it is present.
    inflater.inflate(R.menu.menu_customer_view_and_search, menu);
    // Associate searchable configuration with the SearchView
    SearchManager searchManager =
            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView =
            (SearchView) menu.findItem(R.id.menu_search).getActionView();
    searchView.setQueryHint("Search Customer");
    searchView.setSearchableInfo(
            searchManager.getSearchableInfo(getComponentName()));

    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            return false;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            if(fragment instanceof CustomerDetailsViewWithModifyAndSearch)
                ((CustomerDetailsViewWithModifyAndSearch)fragment).adapter.getFilter().filter(newText);
            return false;
        }
    });



    return true;
}
}

В методе OnQueryTextChangeListener() используйте ваш адаптер. Я разложил его на фрагменты, так как мой адптер находится во фрагменте. Вы можете использовать адаптер напрямую, если он входит в ваш класс активности.

Это мой подход к расширению ответа @klimat, чтобы не терять анимацию фильтрации.

public void filter(String query){
    int completeListIndex = 0;
    int filteredListIndex = 0;
    while (completeListIndex < completeList.size()){
        Movie item = completeList.get(completeListIndex);
        if(item.getName().toLowerCase().contains(query)){
            if(filteredListIndex < filteredList.size()) {
                Movie filter = filteredList.get(filteredListIndex);
                if (!item.getName().equals(filter.getName())) {
                    filteredList.add(filteredListIndex, item);
                    notifyItemInserted(filteredListIndex);
                }
            }else{
                filteredList.add(filteredListIndex, item);
                notifyItemInserted(filteredListIndex);
            }
            filteredListIndex++;
        }
        else if(filteredListIndex < filteredList.size()){
            Movie filter = filteredList.get(filteredListIndex);
            if (item.getName().equals(filter.getName())) {
                filteredList.remove(filteredListIndex);
                notifyItemRemoved(filteredListIndex);
            }
        }
        completeListIndex++;
    }
}

По сути, он просматривает полный список и добавляет / удаляет элементы в отфильтрованный список один за другим.

Добавьте интерфейс в свой адаптер.

public interface SelectedUser{

    void selectedUser(UserModel userModel);

}

реализовать интерфейс в своей основной деятельности и переопределить метод. @Override public void selectedUser(UserModel userModel) {

    startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));



}

Полное руководство и исходный код: Recyclerview с searchview и onclicklistener

Если вы хотите выполнять поиск по нажатию кнопки, это нормально работает.

      filterIcon.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String strCHR = homeSearchEdit.getText().toString();
        if (homeSearchEdit.getText().toString().length() > 0) {
            ArrayList<ServiceModel> listNew = new ArrayList<>();
            for (int l = 0; l < arrayList.size(); l++) {
                String serviceName = arrayList.get(l).getServiceName().toLowerCase();
                if (serviceName.contains(strCHR.toLowerCase())) {
                    listNew.add(arrayList.get(l));
                }
            }
            recyclerView.setVisibility(View.VISIBLE);
            adapter = new ServiceAdapter(HomeActivity.this, listNew);
            recyclerView.setAdapter(adapter);
        } else {
            recyclerView.setVisibility(View.VISIBLE);
            adapter = new ServiceAdapter(HomeActivity.this, arrayList);
            recyclerView.setAdapter(adapter);
        }
    }
});

где , filterIcon кнопка и homeSearchEdit является EditText(где мы применяем для поиска).

Я рекомендую изменить решение @Xaver Kapeller двумя способами ниже, чтобы избежать проблемы после очистки искомого текста (фильтр больше не работает), поскольку задняя часть адаптера имеет меньший размер, чем список фильтров, и произошло исключение IndexOutOfBoundsException. Таким образом, код необходимо изменить, как показано ниже

public void addItem(int position, ExampleModel model) {
    if(position >= mModel.size()) {
        mModel.add(model);
        notifyItemInserted(mModel.size()-1);
    } else {
        mModels.add(position, model);
        notifyItemInserted(position);
    }
}

И изменить также в функциональности moveItem

public void moveItem(int fromPosition, int toPosition) {
    final ExampleModel model = mModels.remove(fromPosition);
    if(toPosition >= mModels.size()) {
        mModels.add(model);
        notifyItemMoved(fromPosition, mModels.size()-1);
    } else {
        mModels.add(toPosition, model);
        notifyItemMoved(fromPosition, toPosition); 
    }
}

Надеюсь, что это может помочь вам!

Android предоставилDiffUtil.Callback()иDiffUtil.ItemCallback<T>и они помогают нам красиво фильтровать наше представление ресайклера

DiffUtil — это служебный класс, который вычисляет разницу между двумя списками и выводит список операций обновления, преобразующих первый список во второй.

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

DiffUtil.Callback() используется с RecyclerView.Adapter

и

DiffUtil.ItemCallback используется с ListAdapter

Фильтровать с помощью RecyclerView

Создайте свой RecyclerView Как обычно, переопределяя

      onCreateViewHolder

onBindViewHolder

getItemCount

и расширениеRecyclerView.ViewHolder Class

Точно так же, как вы сделали (это версия фрагментов вашего кода на Kotlin)

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

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

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

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

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

Теперь создайте еще один класс, который будет реализовывать DiffUtil.Callback().

Этот класс поможет преобразовать текущий список recyclerviews в отфильтрованный список.

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

override fun getOldListSize() = oldList.size

override fun getNewListSize() = newList.size

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

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

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

}

В вашей Activity или Fragment Class Setup ваш адаптер и ваш фильтр

      private fun setupAdapter() {

//mItems is the list you will pass to the adapter
        adapter = CardAdapter(mItems)

        recyclerView.adapter = adapter

}

fun filter(searchText : String){

    val newFilter = mItems.filter {

        it.name.lowercase().contains(text.lowercase()) //filterlogic

    }

//Calculate the list of update operations that can covert one list into the other one 
    val diffResult = DiffUtil.calculateDiff(PostsDiffUtilCallback(mItems,newFilter))

    mItems.clear()


    mItems.addAll(newFilter)

//dispatch all updates to the RecyclerView
    diffResult.dispatchUpdatesTo(adapter)

}

Фильтровать с помощью ListAdapter

Мы будем использовать фильтруемый интерфейс, чтобы помочь нам фильтровать (все еще выясняя, почему я не должен просто использовать функцию фильтра, чтобы получить filteredLists и submitList(filteredLists) напрямую)

Создайте свой класс ListAdapter

      class CardAdapter (
private val mItems : List<Movies>) : ListAdapter<Movies, CardAdapter.BillsPackageViewHolder>(MoviesDiffCallback()),
Filterable {

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

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


override fun getFilter(): Filter {

    return object : Filter() {

        override fun performFiltering(constraint: CharSequence?): FilterResults {

            return FilterResults().apply {

                values = if (constraint.isNullOrEmpty())
                    mItems
                else
                    onFilter(mItems, constraint.toString())
            }
        }

        @Suppress("UNCHECKED_CAST")
        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {

            submitList(results?.values as? List<Movies>)

        }
    }

}

fun onFilter(list: List<Movies>, constraint: String) : List<Movies>{

    val filteredList = list.filter {

        it.name.lowercase().contains(constraint.lowercase())

    }

    return filteredList

}

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

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

Теперь создайте еще один класс, который будет реализовывать DiffUtil.ItemCallback.

      class MoviesDiffCallback : DiffUtil.ItemCallback<Movies>() {

override fun areItemsTheSame(oldItem: Movies, newItem: Movies): Boolean {
    return oldItem.someUniqueid == newItem.someUniqueid
}

override fun areContentsTheSame(oldItem: Movies, newItem: Movies): Boolean {
    return oldItem == newItem
}
}

и в вашей MainActivity или Fragment Setup ваш адаптер и ваш фильтр

      private fun setupAdapter() {

    adapter = CardAdapter(mItems)

    recyclerView.adapter = adapter

}

fun filter(searchString : String){

    adapter.filter.filter(searchString)

}
Другие вопросы по тегам