Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


Cómo filtrar un RecyclerView con un SearchView

Estoy intentando implementar el SearchView de la biblioteca de la ayuda. Quiero que el usuario utilice el SearchView para filtrar una List de películas en un RecyclerView .

He seguido algunos tutoriales hasta ahora y he añadido el SearchView a la ActionBar , pero no estoy muy seguro de dónde ir desde aquí. He visto algunos ejemplos pero ninguno de ellos muestra resultados como usted comienza a escribir.

  • ¿Qué diferencia entre el visor estático y no estático en el Adaptador RecyclerView?
  • Serializar un SparseArray <T> con GSON
  • Iniciar sesión a través de servicio web de descanso de android utilizando retroalimentación no funciona
  • Permiso de denegación: ... requiere android.permission.WRITE_EXTERNAL_STORAGE
  • PhoneGap no dispara deviceready en Android 4.2
  • ¿Qué es <permission-tree> en android manifest? ¿Cómo hacer uso de esto?
  • 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<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); } } } 

  • Cordova autenticación de huellas dactilares en el servidor
  • Cómo utilizar Android GradientDrawable
  • Error de "Conexión al servidor no satisfactoria" al iniciar aplicación de teléfono en el emulador de Android
  • Control preciso sobre las animaciones de Androids VectorDrawable
  • Emulador de Android: el mensaje de error "Desafortunadamente, el navegador dejó de funcionar"
  • Problema de instalación de la plataforma 3.2 sdk. "Hecho. No se instaló nada "
  • 7 Solutions collect form web for “Cómo filtrar un RecyclerView con un SearchView”

    Introducción

    Puesto que no es realmente clara su pregunta con qué exactamente usted está teniendo apuro escribí este paseo rápido sobre cómo poner en práctica esta característica, si usted todavía tiene preguntas no dude en preguntar.

    Tengo un ejemplo práctico 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 debe 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 permite empezar.


    Configuración del SearchView

    En la carpeta res/menu cree un nuevo archivo llamado main_menu.xml . En él agregue un elemento y establezca el actionViewClass en android.support.v7.widget.SearchView . Dado que está utilizando la biblioteca de soporte, tiene que utilizar el espacio de nombres de la biblioteca de soporte para establecer el atributo actionViewClass . Su archivo xml debe ser algo como esto:

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

    En tu Fragment o Activity tienes que inflar este menú xml como de costumbre, entonces puedes 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 del filtro más adelante en onQueryTextChange() una vez que hayamos terminado de implementar el Adapter .


    Configuración del 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 sólo su modelo básico que mostrará un texto en el RecyclerView . Este es el diseño que voy a utilizar para mostrar el texto:

     <?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> 

    Como puede ver, utilizo Data Binding. Si nunca ha trabajado con datos vinculantes antes no se desanime! Es muy simple y potente, sin embargo no puedo explicar cómo funciona en el ámbito 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); } } 

    Una vez más nada especial. Sólo utiliza el enlace de datos para enlazar la clase de modelo a esta disposición, como hemos definido en el diseño xml anterior.

    Ahora podemos finalmente llegar a la parte realmente interesante: Escribir el adaptador. Voy a pasar por alto la implementación básica del Adapter y en vez voy a concentrarme en las partes que son relevantes para esta respuesta.

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


    Lista clasificada

    La SortedList es una herramienta completamente increíble 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 manera muy eficiente. Lo único que requiere que hagas es especificar un orden de los elementos. Usted necesita hacer eso mediante la implementación de un método compare() que compara dos elementos en el SortedList al igual que un Comparator . Pero en lugar de ordenar una List se utiliza para ordenar los elementos en el RecyclerView !

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

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

    En los métodos en la parte superior de la devolución de llamada como onMoved , onInserted , etc., debe llamar al método de notificación equivalente de su Adapter . Los tres métodos en la parte inferior compare , areContentsTheSame y areItemsTheSame que tiene que implementar de acuerdo a qué tipo de objetos que desea mostrar y en qué orden estos objetos deben aparecer en la pantalla.

    Vamos a pasar por estos métodos uno por uno:

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

    Este es el método compare() que hablé anteriormente. En este ejemplo 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 podría tener este aspecto:

     private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() { @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. El SortedList utiliza esto para determinar si un evento de cambio debe ser invocado – en otras palabras, si el RecyclerView debe crossfade la versión antigua y nueva. Si las clases modelo tienen una correcta equals() y la implementación hashCode() lo general sólo puede implementar como se hashCode() anteriormente. Si añadimos una implementación equals() y hashCode() a la clase ExampleModel debería ser algo 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 funcionalidad para generar implementaciones equals() y hashCode() para usted con sólo presionar un botón! Así que no tienes que implementarlos tú mismo. Mira en Internet cómo funciona en su IDE!

    Ahora echemos un vistazo al último método:

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

    El SortedList utiliza este método para comprobar si dos elementos se refieren a la misma cosa. En términos más simples (sin explicar cómo funciona SortedList ) se utiliza para determinar si un objeto ya está contenido en la List y si se necesita reproducir una animación de agregar, mover o cambiar. Si sus modelos tienen un id que normalmente se comparan sólo el ID en este método. Si no lo hace necesita averiguar alguna otra manera de comprobar esto, pero sin embargo, terminan la aplicación de esto depende de su aplicación específica. Por lo general, es la opción más sencilla de dar a todos los modelos un id, que podría ser, por ejemplo, el campo de clave principal si está consultando los datos de una base de datos.

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

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

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

    Ahora pasemos a los negocios: Si implementamos el Adapter con una lista SortedList , debería ser algo como esto:

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

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

    ¡Ahora estamos casi terminados! Pero primero necesitamos una forma de agregar o quitar elementos al Adapter . Para ello podemos añadir métodos al Adapter que nos permitan añadir y quitar elementos a la lista 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(); } 

    No necesitamos llamar a ningún método de notificación aquí porque el SortedList ya lo hace para a través de la SortedList.Callback ! Aparte de que la implementación de estos métodos es bastante sencillo con una excepción: el método de eliminación que elimina una List de modelos. Dado que el SortedList tiene sólo un método de eliminación que puede eliminar un solo objeto que tenemos que hacer un bucle sobre la lista y eliminar los modelos uno por uno. Llamar beginBatchedUpdates() al principio agrupa todos los cambios que vamos a hacer a la SortedList juntos y mejora el rendimiento. Cuando llamamos endBatchedUpdates() el RecyclerView es notificado sobre todos los cambios a la vez.

    Además, lo que tienes que entender es que si agregas un objeto a la lista SortedList y ya está en la lista SortedList , no se volverá a agregar. En su lugar, el SortedList utiliza el método areContentsTheSame() para averiguar si el objeto ha cambiado – y si tiene el elemento en el RecyclerView se actualizará.

    De todos modos, lo que normalmente 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 en la lista 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(); } 

    Este método vuelve a reunir todas las actualizaciones para aumentar el rendimiento. El primer bucle está en el reverso ya que quitar un elemento en el comienzo ensuciaría los índices de todos los artículos que vengan después de él y esto puede llevar en algunos casos a los problemas como incoherencias de los datos. Después añadimos la List a la List SortedList usando addAll() para agregar todos los elementos que aún no están en la lista SortedList y – tal como he descrito anteriormente – actualizar todos los elementos que ya están en la lista SortedList pero que han cambiado.

    Y con eso el Adapter está completo. Todo el asunto debería verse así:

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

    Lo único que falta ahora es implementar el filtrado!


    Implementación de la lógica del filtro

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

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

    Nada especial aquí, sólo instanciar el Adapter y establecerlo en el RecyclerView . Después de eso creamos una List de modelos de los nombres de películas en la matriz de MOVIES . A continuación, agregamos todos los modelos a SortedList .

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

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

    Esto es otra vez bastante sencillo. Llamamos el método filter() y pasamos en la List de ExampleModel s, así como la cadena de consulta. A continuación, llamar a replaceAll() en el Adapter y pasar en la List filtrada devuelta por filter() . También tenemos que llamar a scrollToPosition(0) en el RecyclerView para asegurarnos de que el usuario siempre puede ver todos los elementos al buscar algo. De lo contrario, el RecyclerView podría permanecer en una posición desplazada hacia abajo mientras filtra y posteriormente ocultar algunos elementos. Desplazarse hasta la parte superior asegura una mejor experiencia de usuario mientras busca.

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

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

    Lo primero que hacemos aquí es llamar a toLowerCase() en la cadena de consulta. No queremos que nuestra función de búsqueda sea sensible a mayúsculas y minúsculas y llamando a toLowerCase() en todas las cadenas que comparamos podemos asegurar que devolveremos los mismos resultados independientemente del caso. A continuación, sólo itera a través de todos los modelos de la List que pasó en ella y comprueba si la cadena de consulta está contenido 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 API 7 y superior y comenzando con el nivel 11 de la API obtendrá animaciones de artículos 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 que podemos generalizar todo este problema y hacer la implementación de un Adapter basado en un SortedList mucho más simple.


    Generalizar el problema y simplificar 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 sobre el desbordamiento de pila, sino también porque la mayoría de ellos ya se explicó anteriormente – pero para resumir los cambios: Podemos implementar un Adapter base Que ya se ocupa de tratar con el SortedList , así como los modelos de ViewHolder instancias de ViewHolder y proporciona una manera conveniente de implementar un Adapter basado en un SortedList . Para eso tenemos que hacer dos cosas:

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

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

    SortedListAdapter

    Debido al límite de caracteres para las respuestas en StackOverflow no puedo pasar por cada paso de la implementación de esta clase base o incluso añadir el código fuente completo aquí, pero usted puede encontrar el código fuente completo de esta clase base – lo llamé SortedListAdapter – en Este GitHub Gist .

    Para hacer su vida sencilla he publicado una biblioteca en jCenter que contiene el SortedListAdapter ! Si desea utilizarlo, todo lo que necesita hacer es añadir 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 principal de la biblioteca .

    Usando el SortedListAdapter

    Para usar SortedListAdapter tenemos que hacer dos cambios:

    • Cambie el ViewHolder para que se extienda SortedListAdapter.ViewHolder . El parámetro type debe ser el modelo que debe estar enlazado a este ViewHolder – en este caso ExampleModel . Tienes que enlazar datos a tus modelos en performBind() lugar de bind() .

       public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> { private final ItemExampleBinding mBinding; public ExampleViewHolder(ItemExampleBinding binding) { super(binding.getRoot()); mBinding = binding; } @Override protected void performBind(ExampleModel item) { mBinding.setModel(item); } } 
    • Asegúrese de que todos sus modelos implementan la interfaz de ViewModel :

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

    Después de eso solo tenemos que actualizar el ExampleAdapter para extender SortedListAdapter y eliminar todo lo que no necesitamos más. El parámetro type 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 de tipo en 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); } } 

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

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

    Todos los cambios que realice de esta manera se agrupan juntos para aumentar 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 llamar a commit() entonces ninguno de sus cambios serán aplicados!

    Todo lo que necesitas 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, simplemente 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 agenda telefónica por nombre y número de teléfono.

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

     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; GenericRecycleAdapter.this.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; } } 

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

     public class customerAdapter extends GenericRecycleAdapter<CustomerModel> 

    O simplemente cambie el E al tipo que desea ( <CustomerModel> por ejemplo)

    Luego de searchView (el widget que puede 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; } }); 

    Recomiendo modificar la solución de @Xaver Kapeller con 2 cosas a continuación para evitar un problema después de borrar el texto buscado (el filtro no funcionó más) debido a la parte posterior de la lista de adaptador tiene un tamaño menor que la lista de filtros y el IndexOutOfBoundsException sucedió. Por lo tanto, el código debe modificarse como se indica a continuación

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

    Y modificar también en la funcionalidad 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); } } 

    ¡Espero que pueda ayudarte!

    I followed these steps and it worked well, not so complicated and it works pretty good we are surfing around same sittuation with different ways Hope it helps

    simply create two list in adapter one orignal and one temp and implements Filterable .

      @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { final FilterResults oReturn = new FilterResults(); final ArrayList<T> results = new ArrayList<>(); if (origList == null) origList = new ArrayList<>(itemList); if (constraint != null && constraint.length() > 0) { if (origList != null && origList.size() > 0) { for (final T cd : origList) { if (cd.getAttributeToSearch().toLowerCase() .contains(constraint.toString().toLowerCase())) results.add(cd); } } oReturn.values = results; oReturn.count = results.size();//newly Aded by ZA } else { oReturn.values = origList; oReturn.count = origList.size();//newly added by ZA } return oReturn; } @SuppressWarnings("unchecked") @Override protected void publishResults(final CharSequence constraint, FilterResults results) { itemList = new ArrayList<>((ArrayList<T>) results.values); // FIXME: 8/16/2017 implement Comparable with sort below ///Collections.sort(itemList); notifyDataSetChanged(); } }; } 

    dónde

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

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

    //Filter class

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

    }

    //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.

    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.