Cómo filtrar un RecyclerView con un SearchView

Estoy tratando de implementar SearchView desde la biblioteca de soporte. Quiero que el usuario use SearchView para filtrar una List de películas en un RecyclerView .

He seguido algunos tutoriales hasta ahora y he agregado SearchView a ActionBar , pero no estoy seguro de a dónde ir desde aquí. He visto algunos ejemplos, pero ninguno de ellos muestra resultados cuando comienza a escribir.

Esta es mi 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); } } 

Y este es mi 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); } } } 

Introducción

Debido a que no está del todo claro su pregunta con respecto a qué es exactamente lo que está teniendo problemas, escribí este breve tutorial sobre cómo implementar esta característica; si todavía tiene preguntas, no dude en preguntar.

Tengo un ejemplo de trabajo de todo lo que estoy hablando aquí en este Repositorio de GitHub .
Si desea saber más sobre el proyecto de ejemplo, visite la página principal del proyecto .

En cualquier caso, el resultado debería ser algo como esto:

imagen de demostración

Si primero quieres jugar con la aplicación de demostración, puedes instalarla desde Play Store:

Consiguelo en google play

De todos modos, comencemos.


Configurando SearchView

En la carpeta res/menu crea un nuevo archivo llamado main_menu.xml . En él, agregue un elemento y configure actionViewClass en android.support.v7.widget.SearchView . Como está utilizando la biblioteca de soporte, debe usar el espacio de nombre de la biblioteca de soporte para establecer el atributo actionViewClass . Su archivo xml debería verse más o menos así:

    

En su Fragment o Activity debe inflar este menú xml como de costumbre, luego puede buscar el MenuItem que contiene el SearchView e implementar el OnQueryTextListener que vamos a usar para escuchar los cambios en el texto ingresado en el 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; } 

Y ahora el SearchView está listo para ser utilizado. Implementaremos la lógica de filtro más adelante en onQueryTextChange() una vez que hayamos terminado de implementar el Adapter .


Configurando el Adapter

En primer lugar, esta es la clase de modelo que voy a utilizar para este ejemplo:

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

Es solo su modelo básico el que mostrará un texto en RecyclerView . Este es el diseño que voy a usar para mostrar el texto:

          

Como pueden ver, utilizo Enlace de datos. Si nunca antes trabajó con el enlace de datos, no se desanime. Es muy simple y poderoso, sin embargo, no puedo explicar cómo funciona en el scope de esta respuesta.

Este es el ViewHolder para la clase 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); } } 

De nuevo, nada especial. Simplemente utiliza el enlace de datos para vincular la clase de modelo a este diseño, tal como lo hemos definido en el xml de diseño anterior.

Ahora podemos finalmente llegar a la parte realmente interesante: escribir el adaptador. Voy a omitir la implementación básica del Adapter y en su lugar me voy a concentrar en las partes que son relevantes para esta respuesta.

Pero primero hay una cosa de la que tenemos que hablar: la clase SortedList .


SortedList

SortedList es una herramienta completamente sorprendente que forma parte de la biblioteca RecyclerView . Se encarga de notificar al Adapter sobre los cambios en el conjunto de datos y lo hace de una manera muy eficiente. Lo único que requiere que haga es especificar un orden de los elementos. SortedList hacerlo implementando un método compare() que compare dos elementos en SortedList como un Comparator . ¡Pero en lugar de ordenar una List , se usa para ordenar los artículos en RecyclerView !

SortedList interactúa con el Adapter través de una clase de Callback que debe implementar:

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

En los métodos en la parte superior de la callback como onMoved , onInserted , etc., debe llamar al método de notificación equivalente de su Adapter . Los tres métodos en la parte inferior se compare , areContentsTheSame y areItemsTheSame que debe implementar según el tipo de objetos que desea visualizar y en qué orden deben aparecer en la pantalla.

Repasemos estos métodos uno por uno:

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

Este es el método de compare() que hablé anteriormente. En este ejemplo, solo estoy pasando la llamada a un Comparator que compara los dos modelos. Si desea que los elementos aparezcan en orden alfabético en la pantalla. Este comparador puede verse así:

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

Ahora echemos un vistazo al siguiente método:

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

El propósito de este método es determinar si el contenido de un modelo ha cambiado. SortedList utiliza esto para determinar si un evento de cambio debe invocarse; en otras palabras, si RecyclerView debe realizar un fundido cruzado entre la versión anterior y la nueva. Si las clases modelo tienen una implementación equals() y hashCode() correctas, por lo general solo puede implementarlo como se hashCode() arriba. Si agregamos una implementación equals() y hashCode() a la clase ExampleModel , debería verse más o menos así:

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

Nota rápida: la mayoría de los IDE, como Android Studio, IntelliJ y Eclipse, tienen la funcionalidad de generar implementaciones equals() y hashCode() con solo presionar un botón. Entonces no tiene que implementarlos usted mismo. ¡Busca en Internet cómo funciona en tu IDE!

Ahora echemos un vistazo al último método:

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

SortedList utiliza este método para verificar si dos elementos se refieren a lo mismo. En términos simples (sin explicar cómo funciona SortedList ) esto se usa para determinar si un objeto ya está contenido en la List y si es necesario reproducir una animación de agregar, mover o cambiar. Si sus modelos tienen una identificación, normalmente compararía solo la identificación en este método. Si no lo hacen, necesitas encontrar alguna otra forma de verificar esto, pero como termines implementando esto, depende de tu aplicación específica. Por lo general, es la opción más sencilla para dar a todos los modelos una identificación, que podría ser, por ejemplo, el campo de clave principal si está consultando los datos de una base de datos.

Con SortedList.Callback correctamente implementado, podemos crear una instancia de SortedList :

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

Como primer parámetro en el constructor de SortedList necesita pasar la clase de sus modelos. El otro parámetro es solo SortedList.Callback que definimos anteriormente.

Ahora, vamos al SortedList : si implementamos el Adapter con SortedList , debería verse más o menos así:

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

El Comparator utilizado para ordenar el elemento se pasa a través del constructor para que podamos usar el mismo Adapter incluso si se supone que los elementos se mostrarán en un orden diferente.

¡Ahora casi terminamos! Pero primero necesitamos una manera de agregar o quitar elementos al Adapter . Para este propósito, podemos agregar métodos al Adapter que nos permiten agregar y eliminar elementos a 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(); } 

No necesitamos llamar a ningún método de notificación aquí porque SortedList ya lo hace a través de SortedList.Callback . Aparte de eso, la implementación de estos métodos es bastante directa con una excepción: el método remove que elimina una List de modelos. Dado que SortedList tiene solo un método de eliminación que puede eliminar un solo objeto, debemos recorrer la lista y eliminar los modelos uno por uno. Llamar a beginBatchedUpdates() al principio agrupa todos los cambios que haremos en SortedList y mejora el rendimiento. Cuando llamamos a endBatchedUpdates() se le informa a RecyclerView acerca de todos los cambios a la vez.

Además, lo que debe comprender es que si agrega un objeto a SortedList y ya está en SortedList , no se volverá a agregar. En cambio, SortedList usa el método areContentsTheSame() para determinar si el objeto ha cambiado, y si tiene el elemento en el RecyclerView se actualizará.

De todos modos, lo que generalmente prefiero es un método que me permite reemplazar todos los elementos en el RecyclerView de una vez. Elimine todo lo que no esté en la List y agregue todos los elementos que faltan de 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(); } 

Este método vuelve a unir todas las actualizaciones para boost el rendimiento. El primer ciclo está en reversa, ya que eliminar un elemento al principio arruinaría los índices de todos los elementos que aparecen después de él y esto puede ocasionar problemas como inconsistencias en los datos. Después de eso, simplemente agregamos la List a SortedList usando addAll() para agregar todos los elementos que no están ya en SortedList y, tal como lo describimos anteriormente, actualizamos todos los artículos que ya están en SortedList pero que han cambiado.

Y con eso, el Adapter está completo. Todo debe verse más o menos así:

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

¡Lo único que falta ahora es implementar el filtrado!


Implementando la lógica del filtro

Para implementar la lógica del filtro, primero tenemos que definir una List de todos los modelos posibles. Para este ejemplo, creo una List de instancias ExampleModel partir de una matriz de películas:

 private static final String[] MOVIES = new String[]{ ... }; 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); } 

No sucede nada especial aquí, solo instanciamos el Adapter y lo configuramos en RecyclerView . Después de eso, creamos una List de modelos a partir de los nombres de las películas en la matriz MOVIES . Luego agregamos todos los modelos a SortedList .

Ahora podemos volver a onQueryTextChange() que definimos anteriormente y comenzar a implementar la lógica de filtro:

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

Esto es nuevamente bastante directo. Llamamos al método filter() y pasamos en la List de ExampleModel de ExampleModel así como en la cadena de consulta. Luego llamamos a replaceAll() en el Adapter y pasamos la List filtrada devuelta por filter() . También debemos llamar a scrollToPosition(0) en RecyclerView para garantizar que el usuario siempre pueda ver todos los elementos cuando busca algo. De lo contrario, el RecyclerView podría permanecer en una posición desplazada hacia abajo durante el filtrado y posteriormente ocultar algunos elementos. Desplazarse hacia arriba garantiza una mejor experiencia de usuario durante la búsqueda.

Lo único que queda por hacer ahora es implementar filter() sí mismo:

 private static List filter(List models, String query) { final String 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; } 

Lo primero que hacemos aquí es llamar a toLowerCase() en la cadena de consulta. No deseamos que nuestra función de búsqueda toLowerCase() mayúsculas y minúsculas y, al llamar a toLowerCase() en todas las cadenas que comparamos, podemos garantizar que devolvemos los mismos resultados independientemente del caso. Luego simplemente itera a través de todos los modelos en la List que le pasamos y verifica si la cadena de consulta está contenida en el texto del modelo. Si es así, el modelo se agrega a la List filtrada.

¡Y eso es! El código anterior se ejecutará en el nivel 7 de API y superior y, a partir del nivel 11 de la API, obtienes animaciones de elementos de forma gratuita.

Me doy cuenta de que esta es una descripción muy detallada que probablemente hace que todo esto parezca más complicado de lo que realmente es, pero hay una manera en que podemos generalizar todo este problema y hacer que la implementación de un Adapter basado en SortedList mucho más simple.


Generalizando el problema y simplificando el Adaptador

En esta sección no voy a entrar en muchos detalles, en parte porque estoy corriendo contra el límite de caracteres para las respuestas en Stack Overflow pero también porque la mayoría ya se explicó anteriormente, pero para resumir los cambios: podemos implementar una base Adapter clase que ya se ocupa de lidiar con SortedList y de los modelos vinculantes para ViewHolder instancias de ViewHolder y proporciona una forma conveniente de implementar un Adapter basado en una SortedList . Para eso tenemos que hacer dos cosas:

  • Necesitamos crear una interfaz ViewModel que todas las clases de modelo tengan que implementar
  • Necesitamos crear una subclase ViewHolder que defina un método bind() que el Adapter puede usar para unir modelos automáticamente.

Esto nos permite centrarnos solo en el contenido que se supone que se muestra en RecyclerView simplemente implementando los modelos y las implementaciones correspondientes de ViewHolder . Usando esta clase base no tenemos que preocuparnos por los detalles intrincados del Adapter y su SortedList .

SortedListAdapter

Debido al límite de caracteres para las respuestas en StackOverflow no puedo seguir cada paso de implementación de esta clase base o incluso agregar aquí el código fuente completo, pero puedes encontrar el código fuente completo de esta clase base, lo llamé SortedListAdapter , en este GitHub Gist .

Para simplificar tu vida, he publicado una biblioteca en jCenter que contiene SortedListAdapter . Si desea usarlo, todo lo que necesita hacer es agregar esta dependencia al archivo build.gradle de su aplicación:

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

Puede encontrar más información sobre esta biblioteca en la página de inicio de la biblioteca .

Usando SortedListAdapter

Para usar SortedListAdapter tenemos que hacer dos cambios:

  • Cambie el ViewHolder para que extienda SortedListAdapter.ViewHolder . El parámetro tipo debe ser el modelo que debe vincularse a este ViewHolder , en este caso ExampleModel . performBind() vincular los datos a sus modelos en performBind() lugar 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); } } 
  • Asegúrese de que todos sus modelos implementen la interfaz de ViewModel :

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

Después de eso solo tenemos que actualizar el ExampleAdapter para ampliar SortedListAdapter y eliminar todo lo que ya no necesitamos. El parámetro tipo debe ser el tipo de modelo con el que está trabajando, en este caso ExampleModel . Pero si está trabajando con diferentes tipos de modelos, establezca el parámetro tipo en 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); } } 

¡Después de eso hemos terminado! Sin embargo, una última cosa para mencionar: SortedListAdapter no tiene los mismos métodos add() , remove() o replaceAll() que nuestro ExampleAdapter original. Utiliza un objeto Editor separado para modificar los elementos de la lista a los que se puede acceder a través del método edit() . Por lo tanto, si desea eliminar o agregar elementos, debe llamar a edit() luego agregue y elimine los elementos en esta instancia de Editor y, una vez que haya terminado, llame a commit() para aplicar los cambios a SortedList :

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

Todos los cambios que realice de esta manera se agrupan para boost el rendimiento. El método replaceAll() que implementamos en los capítulos anteriores también está presente en este objeto Editor :

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

Si olvida invocar commit() , ¡no se aplicará ninguno de sus cambios!

Todo lo que necesita hacer es agregar el método de filter en 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 se inicializa en el constructor del adaptador como itemsCopy.addAll(items) .

Si lo hace, solo llame al 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; } }); 

Es un ejemplo de filtrar mi directorio telefónico por nombre y número de teléfono.

Siguiendo a @Shruthi Kamoji de una manera más limpia, podemos usar un filtro, es para eso:

 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.toString().toLowerCase()); } FilterResults results = new FilterResults(); results.values = filteredResults; return results; } }; } protected List getFilteredResults(String constraint) { List results = new ArrayList<>(); for (E item : originalList) { if (item.getName().toLowerCase().contains(constraint)) { results.add(item); } } return results; } } 

El E aquí es un Tipo genérico, puede extenderlo usando su clase:

 public class customerAdapter extends GenericRecycleAdapter 

O simplemente cambie la E por el tipo que desee ( por ejemplo)

Luego, desde searchView (el widget que puedes poner en 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; } }); 

simplemente cree dos listas en el adaptador one orignal y una temp e implementa Filterable .

  @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.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) results.values); // FIXME: 8/16/2017 implement Comparable with sort below ///Collections.sort(itemList); notifyDataSetChanged(); } }; } 

dónde

 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(String query) { return false; } @Override public boolean onQueryTextChange(String 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 actividad:

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