Comment filtrer une vue RecyclerView avec une vue de recherche
j'essaie de mettre en œuvre le SearchView
à partir de la bibliothèque de soutien. Je veux que l'utilisateur soit d'utiliser le "151920920 de" filtrer List
de films dans un RecyclerView
.
j'ai suivi quelques tutoriels jusqu'à présent et j'ai ajouté le SearchView
à la ActionBar
, mais je ne suis pas vraiment sûr où aller à partir d'ici. J'ai vu quelques exemples, mais aucun d'entre eux montrent des résultats que vous commencez à taper.
C'est mon 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);
}
}
Et voici mon 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 réponses
Introduction
Puisqu'il n'est pas vraiment clair de la forme de votre question avec ce que exactement vous avez des problèmes, j'ai écrit cette revue rapide sur la façon de mettre en œuvre cette fonctionnalité, si vous avez encore des questions n'hésitez pas à poser.
j'ai un exemple concret de tout ce dont je parle ici dans ce dépôt GitHub .
Si vous voulez savoir pour en savoir plus sur le projet exemple, visitez le page d'accueil du projet .
dans tous les cas le résultat devrait ressembler à quelque chose comme ceci:
si vous voulez d'abord jouer avec l'application de démonstration, vous pouvez l'installer à partir de la boutique de jeu:
commençons.
mise en place du SearchView
dans le dossier res/menu
créez un nouveau fichier appelé main_menu.xml
. Dans ce ajouter un article et mettre le actionViewClass
à android.support.v7.widget.SearchView
. Puisque vous utilisez la bibliothèque de soutien, vous devez utiliser l'espace de noms de la bibliothèque de soutien pour définir l'attribut actionViewClass
. Votre fichier xml devrait ressembler à quelque chose comme ceci:
<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>
dans votre Fragment
ou Activity
vous devez gonfler ce menu xml comme d'habitude, alors vous pouvez chercher le MenuItem
qui contient le SearchView
et mettre en œuvre le OnQueryTextListener
que nous allons utiliser pour écouter les changements au texte entré dans le 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;
}
Et maintenant le SearchView
est prêt à être utilisé. Nous mettrons en œuvre la logique du filtre plus tard dans onQueryTextChange()
une fois que nous aurons terminé mise en œuvre du Adapter
.
mise en place du Adapter
D'abord et avant tout c'est la classe de modèle que je vais utiliser pour cet exemple:
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;
}
}
c'est juste votre modèle de base qui affichera un texte dans le RecyclerView
. Voici la disposition que je vais utiliser pour afficher le texte:
<?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>
comme vous pouvez le voir j'utilise la liaison de données. Si vous n'avez jamais travaillé avec data binding avant ne soyez pas découragé! Il est très simple et puissant, mais je ne peux pas expliquer comment il fonctionne dans la portée de cette réponse.
C'est le ViewHolder
pour la classe 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);
}
}
encore rien de spécial. Il utilise simplement la liaison de données pour lier la classe model à cette mise en page comme nous l'avons défini dans le xml de mise en page ci-dessus.
maintenant nous pouvons enfin venir à la partie vraiment intéressante: écrire L'Adaptateur. Je vais passer outre la mise en œuvre de base du Adapter
et je vais plutôt me concentrer sur les parties qui sont pertinentes pour cette réponse.
mais il y a d'abord une chose dont nous devons parler: SortedList
classe.
SortedList
le SortedList
est un outil complètement étonnant qui fait partie de la bibliothèque RecyclerView
. Il prend soin d'informer le Adapter
des modifications apportées à l'ensemble de données et il le fait d'une manière très efficace. La seule chose qu'il vous oblige à faire est de spécifier un ordre des éléments. Vous devez le faire en mettant en œuvre une méthode compare()
qui compare deux éléments dans le SortedList
tout comme un Comparator
. Mais au lieu de trier un List
il est utilisé pour trier les articles dans le RecyclerView
!
le SortedList
interagit avec le Adapter
à travers une classe Callback
que vous devez implémenter:
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();
}
}
dans les méthodes en haut du callback comme onMoved
, onInserted
, etc. vous devez appeler la méthode de notification équivalente de votre Adapter
. Les trois méthodes en bas compare
, areContentsTheSame
et areItemsTheSame
vous devez mettre en œuvre selon quel type d'objets vous voulez afficher et dans quel ordre ces objets doit apparaître sur l'écran.
voyons ces méthodes une par une:
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
c'est la méthode compare()
dont j'ai parlé plus tôt. Dans cet exemple, Je ne fais que passer l'appel à un Comparator
qui compare les deux modèles. Si vous voulez que les éléments apparaissent dans l'ordre alphabétique sur l'écran. Ce comparateur pourrait ressembler à ceci:
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
maintenant regardons la méthode suivante:
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
Le but de cette méthode est de déterminer si le contenu d'un modèle a changé. Le SortedList
utilise ceci pour déterminer si un événement de changement doit être invoqué - en d'autres termes si le RecyclerView
doit croiser l'ancienne et la nouvelle version. Si les classes de modèles ont une implémentation correcte equals()
et hashCode()
, vous pouvez généralement l'implémenter comme ci-dessus. Si on ajoute un equals()
et hashCode()
mise en œuvre de la classe ExampleModel
il devrait ressembler à quelque chose comme ceci:
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;
}
}
note rapide: la plupart des IDE comme Android Studio, IntelliJ et Eclipse ont la fonctionnalité de générer des implémentations equals()
et hashCode()
pour vous en appuyant sur un bouton! Si vous n'avez pas à les mettre en œuvre vous-même. Regardez sur internet Comment ça marche dans votre IDE!
voyons maintenant la dernière méthode:
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
Le SortedList
utilise cette méthode pour vérifier si deux éléments font référence à la même chose. En termes simples (sans expliquer comment fonctionne le SortedList
), ceci est utilisé pour déterminer si un objet est déjà contenu dans le List
et si une animation add, move ou change doit être jouée. Si vos modèles ont un id, vous comparez habituellement juste l'id dans cette méthode. Si elles ne vous besoin de trouver un autre moyen de vérifier cela, mais cependant, vous finissez par mettre en œuvre cela dépend de votre application spécifique. Habituellement, c'est l'option la plus simple pour donner à tous les Modèles un id - qui pourrait par exemple être le champ clé primaire si vous interrogez les données d'une base de données.
avec le SortedList.Callback
correctement mis en œuvre, nous pouvons créer une instance du SortedList
:
final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);
comme premier paramètre dans le constructeur du SortedList
vous devez passer la classe de votre modèle. L'autre paramètre est juste le SortedList.Callback
nous avons défini ci-dessus.
maintenant passons aux affaires: si nous mettons en œuvre le Adapter
avec un SortedList
il devrait ressembler à quelque chose comme ceci:
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();
}
}
le Comparator
utilisé pour trier l'article est passé par le constructeur de sorte que nous pouvons utiliser le même Adapter
même si les articles sont censés être affichés dans un ordre différent.
maintenant nous sont presque fait! Mais nous avons d'abord besoin d'un moyen d'ajouter ou de supprimer des éléments de la Adapter
. À cette fin, nous pouvons ajouter des méthodes au Adapter
qui nous permettent d'ajouter et de supprimer des articles au 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();
}
nous n'avons pas besoin d'appeler des méthodes de notification ici parce que le SortedList
le fait déjà à travers le SortedList.Callback
! En dehors de cela, la mise en œuvre de ces méthodes est assez simple avec une exception: la méthode supprimer qui supprime un List
de modèles. Comme le SortedList
n'a qu'une seule méthode de suppression qui peut supprimer un seul objet, nous devons boucler la liste et supprimer les Modèles un par un. Le fait d'appeler beginBatchedUpdates()
au début permet d'effectuer tous les changements que nous allons apporter au SortedList
ensemble et d'améliorer les performances. Lorsque nous appelons endBatchedUpdates()
le RecyclerView
est informé de tous les changements à la fois.
en outre, ce que vous devez comprendre est que si vous ajoutez un objet au SortedList
et qu'il est déjà dans le SortedList
, il ne sera plus ajouté. Au lieu de cela, le SortedList
utilise la méthode areContentsTheSame()
pour déterminer si l'objet a changé - et s'il a l'article dans le RecyclerView
sera mis à jour.
quoi qu'il en soit, ce que je préfère habituellement est une méthode qui me permet de remplacer tous les articles dans le RecyclerView
à la fois. Supprimer tout ce qui n'est pas dans le List
et d'ajouter tous les éléments qui sont manquant dans le 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();
}
cette méthode regroupe de nouveau toutes les mises à jour pour augmenter la performance. La première boucle est inversée car supprimer un élément au début de la boucle perturberait les index de tous les éléments qui surgissent après lui et cela peut conduire dans certains cas à des problèmes comme des incohérences de données. Après cela, nous ajoutons simplement le List
au SortedList
en utilisant addAll()
pour ajouter tous les articles qui ne sont pas déjà dans le SortedList
et - comme je l'ai décrit ci-dessus-mettre à jour tous les éléments qui sont déjà dans le SortedList
mais ont changé.
et que le Adapter
est complet. Le tout devrait ressembler à quelque chose comme ceci:
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();
}
}
la seule chose qui manque maintenant est d'implémenter le filtrage!
mise en œuvre de la logique du filtre
pour mettre en œuvre la logique de filtre nous tout d'abord définir un List
de tous les modèles possibles. Pour cet exemple je crée un List
de ExampleModel
instances à partir d'un tableau de films:
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);
}
il ne se passe rien de spécial ici, nous instancions juste le Adapter
et le plaçons sur le RecyclerView
. Après cela, nous créons un List
de modèles à partir des noms de films dans le tableau MOVIES
. Ensuite, nous ajoutons tous les modèles au SortedList
.
maintenant nous peut revenir à onQueryTextChange()
que nous avons défini plus tôt et commencer à mettre en œuvre la logique de filtre:
@Override
public boolean onQueryTextChange(String query) {
final List<ExampleModel> filteredModelList = filter(mModels, query);
mAdapter.replaceAll(filteredModelList);
mBinding.recyclerView.scrollToPosition(0);
return true;
}
C'est encore assez simple. Nous appelons la méthode filter()
et passer dans le List
de ExampleModel
s ainsi que la chaîne de requête. Nous appelons ensuite replaceAll()
sur le Adapter
et passer dans le filtré List
retourné par filter()
. Nous avons aussi appeler scrollToPosition(0)
sur le RecyclerView
pour s'assurer que l'utilisateur peut toujours voir tous les éléments lors de la recherche de quelque chose. Dans le cas contraire, le RecyclerView
pourrait rester dans une position défilante tout en filtrant et ensuite cacher quelques articles. Défilement vers le haut assure une meilleure expérience utilisateur lors de la recherche.
la seule chose à faire maintenant est de mettre en œuvre filter()
lui-même:
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;
}
la première chose que nous faisons ici est d'appeler toLowerCase()
sur la chaîne de requête. Nous ne voulons pas de notre fonction de recherche pour être sensible à la casse et en appelant toLowerCase()
sur toutes les chaînes que nous comparons, nous pouvons nous assurer que nous retournons les mêmes résultats quel que soit le cas. Puis parcourt tous les modèles de la List
nous avons passé en elle et vérifie si la chaîne de requête est contenue dans le texte du modèle. Si c'est le cas, le modèle est ajouté au filtre List
.
Et c'est tout! Le code ci-dessus sera exécuté au niveau 7 de L'API et au-dessus et à partir du niveau 11 de L'API vous obtiendrez des animations gratuites!
je me rends compte que c'est une description très détaillée qui rend probablement tout cela plus compliqué qu'il ne l'est réellement, mais il y a un moyen de généraliser tout ce problème et de rendre la mise en œuvre d'un Adapter
basé sur un SortedList
beaucoup plus simple.
généraliser le problème et simplifier L'Adaptateur
dans cette section Je ne suis pas je vais entrer dans beaucoup de détails - en partie parce que je suis en train de courir contre la limite de caractères pour les réponses sur le débordement de la pile mais aussi parce que la plupart de cela a déjà expliqué ci - dessus-mais pour résumer les changements: nous pouvons implémenter une classe de base Adapter
qui s'occupe déjà du traitement des SortedList
ainsi que des modèles de liaison aux instances ViewHolder
et fournit un moyen pratique pour implémenter un Adapter
basé sur un SortedList
. Pour cela, nous devons faire deux choses:
- nous devons créer une interface
ViewModel
que toutes les classes de modèles doivent implémenter - nous devons créer une sous-classe
ViewHolder
qui définit une méthodebind()
que leAdapter
peut utiliser pour lier des modèles automatiquement.
cela nous permet de nous concentrer uniquement sur le contenu qui est censé être affiché dans le RecyclerView
en implémentant simplement les modèles et là mise en œuvre ViewHolder
correspondante. En utilisant cette classe de base, nous n'avons pas à nous soucier des détails complexes du Adapter
et de son SortedList
.
SortedListAdapter
en raison de la limite de caractères pour les réponses sur StackOverflow Je ne peux pas passer par chaque étape de la mise en œuvre de cette classe de base ou même ajouter le code source complet ici, mais vous pouvez trouver le code source complet de cette classe de base - je l'ai appelé SortedListAdapter
- en ce Github Gist .
pour vous simplifier la vie, j'ai publié une bibliothèque sur jCenter qui contient le SortedListAdapter
! Si vous voulez l'utiliser, alors tout ce que vous devez faire est d'ajouter cette dépendance à votre application construire.gradle fichier:
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
vous pouvez trouver plus d'information sur cette bibliothèque sur la page d'accueil de la bibliothèque .
en utilisant le désembueur
pour utiliser le SortedListAdapter
nous devons faire deux changements:
-
changer le
ViewHolder
de sorte qu'il s'étendSortedListAdapter.ViewHolder
. Le paramètre type doit être le modèle qui doit être lié à ceViewHolder
- dans ce casExampleModel
. Vous devez lier les données à vos modèles dansperformBind()
au lieu debind()
.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); } }
-
assurez-vous que tous vos modèles implémentent l'interface
ViewModel
:public class ExampleModel implements SortedListAdapter.ViewModel { ... }
après cela, nous avons juste à mettre à jour le ExampleAdapter
pour étendre SortedListAdapter
et enlever tout ce dont nous n'avons plus besoin. Le paramètre type doit être le type de modèle avec lequel vous travaillez - dans ce cas ExampleModel
. Mais si vous travaillez avec différents types de modèles, définissez le paramètre type. à 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);
}
}
après cela nous sommes finis! Toutefois, une dernière chose à mentionner: le SortedListAdapter
n'a pas les mêmes add()
, remove()
ou replaceAll()
méthodes notre ExampleAdapter
original avait. Il utilise un objet séparé Editor
pour modifier les éléments de la liste auxquels on peut accéder par la méthode edit()
. Donc, si vous voulez supprimer ou ajouter des éléments vous devez appeler edit()
puis Ajouter et supprimer les éléments sur cette instance Editor
et une fois que vous avez terminé, appelez commit()
sur elle pour appliquer les changements au SortedList
:
mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();
tous les changements que vous faites de cette façon sont assemblés pour augmenter la performance. La méthode replaceAll()
que nous avons mise en œuvre dans les chapitres ci-dessus est également présente sur cet Editor
objet:
mAdapter.edit()
.replaceAll(mModels)
.commit();
si vous oubliez d'appeler commit()
alors aucun de vos changements ne sera appliqué!
Tout ce que vous devez faire est d'ajouter filter
méthode 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
est initialisé dans le constructeur de l'adaptateur comme itemsCopy.addAll(items)
.
si vous le faites, appelez simplement filter
de 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;
}
});
C'est un exemple de filtrage de mon répertoire par nom et numéro de téléphone.
suivant @Shruthi Kamoji d'une manière plus propre, nous pouvons juste utiliser un filtrable, c'est fait pour ça:
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;
}
}
le A ici est un Type générique, vous pouvez l'étendre en utilisant votre classe:
public class customerAdapter extends GenericRecycleAdapter<CustomerModel>
ou tout simplement changer le E au type que vous voulez ( <CustomerModel>
par exemple)
puis à partir de searchView (le widget que vous pouvez mettre dans le 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;
}
});
il suffit de créer deux listes dans l'adaptateur un origal et un temp et met en œuvre filtrable .
@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();
}
};
}
où
public GenericBaseAdapter(Context mContext, List<T> itemList) {
this.mContext = mContext;
this.itemList = itemList;
this.origList = itemList;
}
je recommande de modifier la solution de @Xaver Kapeller avec 2 choses ci-dessous pour éviter un problème après que vous avez effacé le texte recherché (le filtre ne fonctionnait plus) en raison de la liste arrière de l'adaptateur a plus petite taille que la liste de filtre et L'IndexOutOfBoundsException est arrivé. Le code doit donc être modifié comme ci-dessous
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);
}
}
et modifier aussi dans la fonctionnalité 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);
}
}
espérons qu'il pourrait vous aider!
j'ai résolu le même problème en utilisant le lien avec quelques modifications. filtre de recherche sur RecyclerView avec cartes. Est-il même possible? (espérons que cela aide).
voici ma classe d'adaptateur
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);
}
}
/ / classe de filtre
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();
}
}
/ / Classe d'activité
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;
}
}
dans la méthode OnQueryTextChangeListener() utilisez votre adaptateur. Je Je l'ai moulé pour qu'il se fragmente car mon adpter est en fragment. Vous pouvez utiliser l'adaptateur directement si son dans votre classe d'activité.
Dans Adaptateur:
public void setFilter(List<Channel> newList){
mChannels = new ArrayList<>();
mChannels.addAll(newList);
notifyDataSetChanged();
}
En Activité:
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;
}
});