Comment filtrer un RecyclerView avec un SearchView

J’essaie d’implémenter SearchView partir de la bibliothèque de support. Je veux que l’utilisateur utilise SearchView pour filtrer une List de films dans RecyclerView .

J’ai suivi quelques tutoriels jusqu’à présent et j’ai ajouté SearchView au ActionBar , mais je ne suis pas vraiment sûr de savoir où aller à partir de là. J’ai vu quelques exemples, mais aucun ne montre de résultats lorsque vous commencez à taper.

Ceci est ma 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 Adapter mon Adapter :

 public abstract class CardAdapter extends RecyclerView.Adapter implements Filterable { List mItems; public CardAdapter() { super(); mItems = new ArrayList(); 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); } } } 

introduction

Étant donné que la question que vous vous posez ne correspond pas vraiment à ce que vous rencontrez exactement, j’ai écrit cette procédure rapide pour savoir comment implémenter cette fonctionnalité, si vous avez encore des questions, n’hésitez pas à demander.

J’ai un exemple pratique de tout ce dont je parle ici dans ce référentiel GitHub .
Si vous voulez en savoir plus sur l’exemple de projet, visitez la page d’accueil du projet .

Dans tous les cas, le résultat devrait ressembler à ceci:

image de démonstration

Si vous voulez d’abord jouer avec l’application de démonstration, vous pouvez l’installer depuis le Play Store:

Obtenez le sur Google Play

Quoi qu’il en soit, commençons.


Configuration de SearchView

Dans le dossier res/menu créez un nouveau fichier appelé main_menu.xml . Dans elle, ajoutez un élément et définissez l’ actionViewClass sur android.support.v7.widget.SearchView . Puisque vous utilisez la bibliothèque de support, vous devez utiliser l’espace de noms de la bibliothèque de support pour définir l’atsortingbut actionViewClass . Votre fichier XML devrait ressembler à ceci:

    

Dans votre Fragment ou Activity vous devez gonfler ce menu xml comme d’habitude, vous pouvez alors chercher le MenuItem qui contient SearchView et implémenter OnQueryTextListener que nous allons utiliser pour écouter les modifications apscopes au texte entré dans 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(Ssortingng query) { // Here is where we are going to implement the filter logic return false; } @Override public boolean onQueryTextSubmit(Ssortingng query) { return false; } 

Et maintenant, SearchView est prêt à être utilisé. Nous implémenterons plus tard la logique de filtrage dans onQueryTextChange() une fois que nous aurons fini d’implémenter l’ Adapter .


Configuration de l’ Adapter

C’est avant tout la classe de modèle que je vais utiliser pour cet exemple:

 public class ExampleModel { private final long mId; private final Ssortingng mText; public ExampleModel(long id, Ssortingng text) { mId = id; mText = text; } public long getId() { return mId; } public Ssortingng getText() { return mText; } } 

C’est juste votre modèle de base qui affichera un texte dans RecyclerView . C’est la mise en page que je vais utiliser pour afficher le texte:

          

Comme vous pouvez le voir, j’utilise la liaison de données. Si vous n’avez jamais travaillé avec la liaison de données, ne vous découragez pas! C’est très simple et puissant, mais je ne peux pas expliquer comment cela fonctionne dans le cadre de cette réponse.

Ceci 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 une fois rien de spécial. Il utilise simplement la liaison de données pour lier la classe de modèle à cette mise en page, comme nous l’avons défini dans la présentation XML ci-dessus.

Maintenant, nous pouvons enfin arriver à la partie vraiment intéressante: Ecrire l’adaptateur. Je vais sauter sur l’implémentation de base de l’ Adapter et je vais plutôt me concentrer sur les parties pertinentes pour cette réponse.

Mais il faut d’abord parler d’une chose: la classe SortedList .


SortedList

La SortedList est un outil complètement incroyable qui fait partie de la bibliothèque RecyclerView . Il prend soin de notifier l’ Adapter des modifications apscopes à l’dataset et le fait de manière très efficace. La seule chose que vous devez faire est de spécifier un ordre des éléments. Vous devez le faire en implémentant une méthode compare() qui compare deux éléments de la liste SortedList tout comme un Comparator . Mais au lieu de sortinger une List elle est utilisée pour sortinger les éléments dans RecyclerView !

La SortedList interagit avec l’ Adapter via une classe de Callback que vous devez implémenter:

 private final SortedList.Callback mCallback = new SortedList.Callback() { @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 , ce sont les areContentsTheSame et areItemsTheSame vous devez implémenter en fonction du type d’objects que vous souhaitez afficher et de l’ordre dans lequel ces objects doivent apparaître à l’écran.

Passons en revue 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() j’ai parlé plus tôt. Dans cet exemple, je transmets l’appel à un Comparator qui compare les deux modèles. Si vous souhaitez que les éléments apparaissent dans l’ordre alphabétique à l’écran. Ce comparateur pourrait ressembler à ceci:

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

Voyons maintenant 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é. SortedList utilise ceci pour déterminer si un événement de changement doit être appelé – en d’autres termes, si RecyclerView doit fondre l’ancienne et la nouvelle version. Si vous modélisez les classes avec une implémentation equals() et hashCode() correcte, vous pouvez généralement l’implémenter comme ci-dessus. Si nous ajoutons une implémentation equals() et hashCode() à la classe ExampleModel , elle devrait ressembler à ceci:

 public class ExampleModel implements SortedListAdapter.ViewModel { private final long mId; private final Ssortingng mText; public ExampleModel(long id, Ssortingng text) { mId = id; mText = text; } public long getId() { return mId; } public Ssortingng 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; } } 

Remarque rapide: la plupart des IDE comme Android Studio, IntelliJ et Eclipse ont des fonctionnalités pour générer des implémentations equals() et hashCode() appuyant sur un bouton! Donc, vous n’avez pas à les mettre en œuvre vous-même. Recherchez sur Internet comment cela fonctionne dans votre IDE!

Regardons maintenant la dernière méthode:

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

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 SortedList ), cela permet de déterminer si un object est déjà contenu dans la List et si une animation d’ajout, de déplacement ou de modification doit être lue. Si vos modèles ont un identifiant, vous ne comparez généralement que l’id dans cette méthode. Si ce n’est pas le cas, vous devez trouver un autre moyen de vérifier cela, mais cela dépend de votre application spécifique. C’est généralement l’option la plus simple de donner un identifiant à tous les modèles – par exemple, le champ clé principal si vous interrogez les données d’une firebase database.

Avec le SortedList.Callback correctement implémenté, nous pouvons créer une instance de SortedList :

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

En tant que premier paramètre du constructeur de SortedList vous devez passer la classe de vos modèles. L’autre paramètre est juste le SortedList.Callback nous avons défini ci-dessus.

Maintenant, passons aux SortedList : si nous implémentons l’ Adapter avec un SortedList il devrait ressembler à ceci:

 public class ExampleAdapter extends RecyclerView.Adapter { private final SortedList mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback() { @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 mComparator; public ExampleAdapter(Context context, Comparator 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 sortinger l’élément est transmis par le constructeur afin que nous puissions utiliser le même Adapter même si les éléments sont supposés être affichés dans un ordre différent.

Maintenant nous avons presque fini! Mais nous avons d’abord besoin d’un moyen d’append ou de supprimer des éléments à l’ Adapter . Pour cela, nous pouvons append des méthodes à l’ Adapter ce qui nous permet d’append et de supprimer des éléments dans la liste SortedList :

 public void add(ExampleModel model) { mSortedList.add(model); } public void remove(ExampleModel model) { mSortedList.remove(model); } public void add(List models) { mSortedList.addAll(models); } public void remove(List 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 car SortedList le SortedList déjà pour travers SortedList.Callback ! En dehors de cela, l’implémentation de ces méthodes est assez simple, à une exception près: la méthode remove qui supprime une List de modèles. Étant donné que SortedList n’a qu’une méthode de suppression capable de supprimer un seul object, nous devons passer en revue la liste et supprimer les modèles un par un. L’appel de beginBatchedUpdates() au début regroupe toutes les modifications que nous allons apporter à SortedList et améliore les performances. Lorsque nous appelons endBatchedUpdates() RecyclerView est endBatchedUpdates() informé de toutes les modifications.

De plus, vous devez comprendre que si vous ajoutez un object à la liste SortedList et qu’il se trouve déjà dans la liste SortedList il ne sera plus ajouté. Au lieu de cela, SortedList utilise la méthode areContentsTheSame() pour déterminer si l’object a été modifié et si l’élément contient l’élément RecyclerView il sera mis à jour.

Quoi qu’il en soit, ce que je préfère habituellement, c’est une méthode qui me permet de remplacer tous les éléments du RecyclerView en même temps. Supprimez tout ce qui ne figure pas dans la List et ajoutez tous les éléments manquants dans la liste SortedList :

 public void replaceAll(List 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 à nouveau toutes les mises à jour pour améliorer les performances. La première boucle est inversée, car la suppression d’un élément au début gâcherait les index de tous les éléments apparaissant après celui-ci, ce qui peut conduire dans certains cas à des problèmes tels que des incohérences de données. Après cela, nous ajoutons simplement la List à la liste SortedList utilisant addAll() pour append tous les éléments qui ne sont pas déjà dans la liste SortedList et, tout comme je l’ai décrit ci-dessus, SortedList jour tous les éléments qui ont déjà changé.

Et avec cela l’ Adapter est complet. Le tout devrait ressembler à ceci:

 public class ExampleAdapter extends RecyclerView.Adapter { private final SortedList mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback() { @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 mComparator; private final LayoutInflater mInflater; public ExampleAdapter(Context context, Comparator 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 models) { mSortedList.addAll(models); } public void remove(List models) { mSortedList.beginBatchedUpdates(); for (ExampleModel model : models) { mSortedList.remove(model); } mSortedList.endBatchedUpdates(); } public void replaceAll(List 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, c’est d’implémenter le filtrage!


Implémentation de la logique de filtrage

Pour implémenter la logique de filtrage, nous devons d’abord définir une List de tous les modèles possibles. Pour cet exemple, je crée une List d’instances ExampleModel partir d’un tableau de films:

 private static final Ssortingng[] MOVIES = new Ssortingng[]{ ... }; private static final Comparator ALPHABETICAL_COMPARATOR = new Comparator() { @Override public int compare(ExampleModel a, ExampleModel b) { return a.getText().compareTo(b.getText()); } }; private ExampleAdapter mAdapter; private List 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); } 

Rien de spécial ici, nous venons juste d’instancier l’ Adapter et de le configurer sur RecyclerView . Après cela, nous créons une List de modèles à partir des noms de film dans le tableau MOVIES . Ensuite, nous ajoutons tous les modèles à la liste SortedList .

Nous pouvons maintenant revenir à onQueryTextChange() que nous avons défini plus tôt et commencer à implémenter la logique de filtrage:

 @Override public boolean onQueryTextChange(Ssortingng query) { final List 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 passons dans la List des ExampleModel s ainsi que la chaîne de requête. Nous appelons alors replaceAll() sur l’ Adapter et transmettons la List filtrée renvoyée par filter() . Nous devons également appeler scrollToPosition(0) sur RecyclerView pour nous assurer que l’utilisateur peut toujours voir tous les éléments lorsqu’il recherche quelque chose. Sinon, RecyclerView peut restr dans une position déroulée pendant le filtrage et ensuite masquer quelques éléments. Faire défiler vers le haut garantit une meilleure expérience utilisateur lors de la recherche.

Il ne rest plus qu’à implémenter filter() lui-même:

 private static List filter(List models, Ssortingng query) { final Ssortingng lowerCaseQuery = query.toLowerCase(); final List 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 l’appel toLowerCase() sur la chaîne de requête. Nous ne voulons pas que notre fonction de recherche soit sensible à la casse et en appelant toLowerCase() sur toutes les chaînes que nous comparons, nous pouvons nous assurer que nous renvoyons les mêmes résultats, indépendamment du cas. Il ne fait alors qu’itérer tous les modèles de la List nous y avons passés 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é à la List filtrée.

Et c’est tout! Le code ci-dessus s’exécutera sur API niveau 7 et supérieur et à partir de l’API niveau 11, vous obtenez des animations d’objects gratuitement!

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 existe un moyen de généraliser tout ce problème et de simplifier l’implémentation d’un Adapter basé sur SortedList .


Généraliser le problème et simplifier l’adaptateur

Dans cette section, je ne vais pas entrer dans les détails – en partie parce que je suis confronté à la limite de caractères pour les réponses sur Stack Overflow mais aussi parce que la plupart d’entre elles ont déjà été expliquées – mais pour résumer les modifications: classe qui prend déjà en charge la gestion de SortedList ainsi que la liaison des modèles aux instances ViewHolder et fournit un moyen pratique d’implémenter un Adapter basé sur 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éthode bind() l’ Adapter peut utiliser pour lier les modèles automatiquement.

Cela nous permet de nous concentrer sur le contenu qui doit être affiché dans RecyclerView en implémentant simplement les modèles et les implémentations ViewHolder correspondantes. En utilisant cette classe de base, nous n’avons pas à nous soucier des détails complexes de l’ Adapter et de sa liste SortedList .

SortedListAdapter

En raison de la limite de caractères pour les réponses sur StackOverflow, je ne peux pas passer par chaque étape d’implémentation de cette classe de base ni même append le code source complet, mais vous pouvez trouver le code source complet de cette classe de base – SortedListAdapter – in ce GitHub Gist .

Pour vous simplifier la vie, j’ai publié une bibliothèque sur jCenter qui contient SortedListAdapter ! Si vous souhaitez l’utiliser, il vous suffit d’append cette dépendance au fichier build.gradle de votre application:

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

Vous pouvez trouver plus d’informations sur cette bibliothèque sur la page d’accueil de la bibliothèque .

Utilisation de SortedListAdapter

Pour utiliser SortedListAdapter nous devons apporter deux modifications:

  • Modifiez le ViewHolder pour qu’il étend SortedListAdapter.ViewHolder . Le paramètre type devrait être le modèle qui devrait être lié à ce ViewHolder – dans ce cas, ExampleModel . Vous devez lier des données à vos modèles dans performBind() au lieu de bind() .

     public class ExampleViewHolder extends SortedListAdapter.ViewHolder { 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, il suffit de mettre à jour ExampleAdapter pour étendre SortedListAdapter et supprimer tout ce dont nous n’avons plus besoin. Le paramètre type doit correspondre au type de modèle avec ExampleModel vous travaillez – dans ce cas, ExampleModel . Mais si vous travaillez avec différents types de modèles, définissez le paramètre type sur ViewModel .

 public class ExampleAdapter extends SortedListAdapter { public ExampleAdapter(Context context, Comparator comparator) { super(context, ExampleModel.class, comparator); } @Override protected ViewHolder 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 avons fini! Cependant, une dernière chose à mentionner: Le SortedListAdapter n’a pas les mêmes méthodes add() , remove() ou replaceAll() notre exemple ExampleAdapter . Il utilise un object Editor distinct pour modifier les éléments de la liste accessibles via la méthode edit() . Donc, si vous souhaitez supprimer ou append des éléments que vous devez appeler edit() puis ajoutez et supprimez les éléments de cette instance de l’ Editor et, une fois que vous avez terminé, appelez commit() pour appliquer les modifications à SortedList :

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

Toutes les modifications que vous apportez de cette manière sont regroupées pour augmenter les performances. La méthode replaceAll() nous avons implémentée dans les chapitres ci-dessus est également présente sur cet object Editor :

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

Si vous oubliez d’appeler commit() aucune de vos modifications ne sera appliquée!

Tout ce que vous devez faire est d’append une méthode de filter dans RecyclerView.Adapter :

 public void filter(Ssortingng 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 le filter de OnQueryTextListener :

 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(Ssortingng query) { adapter.filter(query); return true; } @Override public boolean onQueryTextChange(Ssortingng 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.

Après @Shruthi Kamoji de manière plus propre, nous pouvons simplement utiliser un filtre, c’est-à-dire:

 public abstract class GenericRecycleAdapter extends RecyclerView.Adapter implements Filterable { protected List list; protected List originalList; protected Context context; public GenericRecycleAdapter(Context context, List 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) results.values; notifyDataSetChanged(); } @Override protected FilterResults performFiltering(CharSequence constraint) { List filteredResults = null; if (constraint.length() == 0) { filteredResults = originalList; } else { filteredResults = getFilteredResults(constraint.toSsortingng().toLowerCase()); } FilterResults results = new FilterResults(); results.values = filteredResults; return results; } }; } protected List getFilteredResults(Ssortingng constraint) { List results = new ArrayList<>(); for (E item : originalList) { if (item.getName().toLowerCase().contains(constraint)) { results.add(item); } } return results; } } 

L’E ici est un type générique, vous pouvez l’étendre en utilisant votre classe:

 public class customerAdapter extends GenericRecycleAdapter 

Ou changez simplement le E pour le type que vous voulez ( par exemple)

Puis à partir de searchView (le widget que vous pouvez mettre sur menu.xml):

 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(Ssortingng text) { return false; } @Override public boolean onQueryTextChange(Ssortingng text) { yourAdapter.getFilter().filter(text); return true; } }); 

Il suffit de créer deux listes dans l’adaptateur un orignal et une temp et implémente Filtrable .

  @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { final FilterResults oReturn = new FilterResults(); final ArrayList 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.getAtsortingbuteToSearch().toLowerCase() .contains(constraint.toSsortingng().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) results.values); // FIXME: 8/16/2017 implement Comparable with sort below ///Collections.sort(itemList); notifyDataSetChanged(); } }; } 

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

I recommend modify the solution of @Xaver Kapeller with 2 things below to avoid a problem after you cleared the searched text (the filter didn’t work anymore) due to the list back of adapter has smaller size than filter list and the IndexOutOfBoundsException happened. So the code need to modify as below

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

And modify also in moveItem functionality

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

Hope that It could help you!

I have solved the same problem using the link with some modifications in it. Search filter on RecyclerView with Cards. Is it even possible? (hope this helps).

Here is my adapter class

 public class ContactListRecyclerAdapter extends RecyclerView.Adapter implements Filterable { Context mContext; ArrayList customerList; ArrayList parentCustomerList; public ContactListRecyclerAdapter(Context context,ArrayList 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); } } 

//Filter class

 import android.widget.Filter; import java.util.ArrayList; public class FilterCustomerSearch extends Filter { private final ContactListRecyclerAdapter mAdapter; ArrayList contactList; ArrayList filteredList; public FilterCustomerSearch(ContactListRecyclerAdapter mAdapter,ArrayList 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) results.values); mAdapter.notifyDataSetChanged(); } 

}

//Activity class

 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(Ssortingng query) { return false; } @Override public boolean onQueryTextChange(Ssortingng newText) { if(fragment instanceof CustomerDetailsViewWithModifyAndSearch) ((CustomerDetailsViewWithModifyAndSearch)fragment).adapter.getFilter().filter(newText); return false; } }); return true; } } 

In OnQueryTextChangeListener() method use your adapter. I have casted it to fragment as my adpter is in fragment. You can use the adapter directly if its in your activity class.

In Adapter:

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

En activité:

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