recyclerview how example ejemplo actionbar java android android-fragments filter searchview

java - how - SearchView no filtra en cada pestaña de TabLayout del niño



searchview android how to use (3)

Puede administrar el filtro en la lista anidada usando un patrón Observable / Observer , esto actualizará cada lista anidada de un padre observable . Arreglé todos los problemas y ahora funciona bien para lograr el comportamiento correcto.

Por lo tanto, aquí está lo que hice para lograrlo:

  1. Usando un SearchView padre en la Activity
  2. (Opcional) Cree una clase de Filter ( android.widget.Filter ) en la lista anidada Adapter
  3. Luego, usando un patrón Observable / Observer para el Fragment anidado con Activity

Antecedentes: cuando probé tu código, tuve tres problemas:

  • No puedo hacer una búsqueda utilizando ActionBar: parece que nunca se llama a onQueryTextChange en Fragment s. Cuando toco en el ícono de búsqueda, me parece que SearchView (texto de edición, ícono, etc.) no se adjunta con el widget de búsqueda (sino que se adjunta al widget de la actividad).
  • No puedo ejecutar el método personalizado filter2 : quiero decir, cuando resolví el punto anterior, este método no funciona. De hecho, tengo que jugar con la clase personalizada que se extiende por Filter y sus dos métodos: performFiltering y publishResults . Sin eso, aparece una pantalla en blanco cuando toco una palabra en la barra de búsqueda. Sin embargo, este podría ser solo mi código y quizás el filter2() funcione perfectamente para usted ...
  • No puedo tener una búsqueda persistente entre fragmentos: para cada fragmento secundario se crea un nuevo SearchView . Me parece que repetidamente llamas a esta línea SearchView sv = new SearchView(...); en fragmento anidado. Así que cada vez que cambio al siguiente fragmento, la vista de búsqueda expandida elimina su valor de texto anterior.

De todos modos, después de algunas investigaciones, encontré esta respuesta en SO sobre la implementación de un fragmento de búsqueda . Casi el mismo código que el suyo, excepto que "duplica" el código del menú de opciones en la actividad principal y en fragmentos. No deberías hacerlo, creo que es la causa de mi primer problema en puntos anteriores.
Además, el patrón utilizado en el enlace de la respuesta (una búsqueda en un fragmento) podría no estar adaptado al suyo (una búsqueda de múltiples fragmentos). Debe llamar a un SearchView en la Activity principal para todos los Fragment anidados.

Solución: Así es como lo logré:

# 1 usando un SearchView padre:

Evitará las funciones duplicadas y permitirá que la actividad de los padres supervise a todos sus hijos. Además, esto evitará su icono de duplicación en el menú.
Esta es la principal clase de Activity principal:

public class ActivityName extends AppCompatActivity implements SearchView.OnQueryTextListener { @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem item = menu.findItem(R.id.action_search); SearchView searchview = new SearchView(this); SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); searchview.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); ... MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); MenuItemCompat.setActionView(item, searchview); searchview.setOnQueryTextListener(this); searchview.setIconifiedByDefault(false); return super.onCreateOptionsMenu(menu); } private void changeSearchViewTextColor(View view) { ... } @Override public boolean onQueryTextSubmit(String query) { return false; } @Override public boolean onQueryTextChange(String newText) { // update the observer here (aka nested fragments) return true; } }

# 2 (opcional) Crear un widget de Filter :

Como dije anteriormente, no puedo hacer que funcione con filter2() , así que creo una clase de Filter como cualquier ejemplo en la web.
Se ve rápidamente como, en el adaptador de fragmento anidado, como sigue:

private ArrayList<String> originalList; // I used String objects in my tests private ArrayList<String> filteredList; private ListFilter filter = new ListFilter(); @Override public int getCount() { return filteredList.size(); } public Filter getFilter() { return filter; } private class ListFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint != null && constraint.length() > 0) { constraint = constraint.toString().toLowerCase(); final List<String> list = originalList; int count = list.size(); final ArrayList<String> nlist = new ArrayList<>(count); String filterableString; for (int i = 0; i < count; i++) { filterableString = list.get(i); if (filterableString.toLowerCase().contains(constraint)) { nlist.add(filterableString); } } results.values = nlist; results.count = nlist.size(); } else { synchronized(this) { results.values = originalList; results.count = originalList.size(); } } return results; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { if (results.count == 0) { notifyDataSetInvalidated(); return; } filteredList = (ArrayList<String>) results.values; notifyDataSetChanged(); } }

# 3 usando un patrón Observable / Observer :

La actividad, con la vista de búsqueda, es el objeto Observable y los fragmentos anidados son los Observer ( vea el patrón Observador ). Básicamente, cuando se onQueryTextChange a onQueryTextChange , se activará el método update() en los observadores existentes.
Aquí está la declaración en la Activity padres:

private static ActivityName instance; private FilterManager filterManager; @Override protected void onCreate(Bundle savedInstanceState) { ... instance = this; filterManager = new FilterManager(); } public static FilterManager getFilterManager() { return instance.filterManager; // return the observable class } @Override public boolean onQueryTextChange(String newText) { filterManager.setQuery(newText); // update the observable value return true; }

Esta es la clase Observable que escuchará y "pasará" los datos actualizados:

public class FilterManager extends Observable { private String query; public void setQuery(String query) { this.query = query; setChanged(); notifyObservers(); } public String getQuery() { return query; } }

Para agregar los fragmentos del observador para escuchar el valor de la vista de búsqueda, lo hago cuando se inicializan en FragmentStatePagerAdapter .
Así que en el fragmento primario, creo las pestañas de contenido al pasar el FilterManager :

private ViewPager pager; private ViewPagerAdapter pagerAdapter; @Override public View onCreateView(...) { ... pagerAdapter = new ViewPagerAdapter( getActivity(), // pass the context, getChildFragmentManager(), // the fragment manager MainActivity.getFilterManager() // and the filter manager ); }

El adaptador agregará el observador al observable principal y lo eliminará cuando se destruyan los fragmentos secundarios.
Aquí está el ViewPagerAdapter del fragmento padre:

public class ViewPagerAdapter extends FragmentStatePagerAdapter { private Context context; private FilterManager filterManager; public ViewPagerAdapter(FragmentManager fm) { super(fm); } public ViewPagerAdapter(Context context, FragmentManager fm, FilterManager filterManager) { super(fm); this.context = context; this.filterManager = filterManager; } @Override public Fragment getItem(int i) { NestedFragment fragment = new NestedFragment(); // see (*) filterManager.addObserver(fragment); // add the observer return fragment; } @Override public int getCount() { return 10; } @Override public void destroyItem(ViewGroup container, int position, Object object) { NestedFragment fragment = (NestedFragment) object; // see (*) filterManager.deleteObserver(fragment); // remove the observer super.destroyItem(container, position, object); } }

Finalmente, cuando se filterManager.setQuery() en actividad con onQueryTextChange() , esto se recibirá en el fragmento update() en el método update() que está implementando Observer .
Estos son los fragmentos anidados con el ListView para filtrar:

public class NestedFragment extends Fragment implements Observer { private boolean listUpdated = false; // init the update checking value ... // setup the listview and the list adapter ... // use onResume to filter the list if it''s not already done @Override public void onResume() { super.onResume(); // get the filter value final String query = MainActivity.getFilterManager().getQuery(); if (listview != null && adapter != null && query != null && !listUpdated) { // update the list with filter value listview.post(new Runnable() { @Override public void run() { listUpdated = true; // set the update checking value adapter.getFilter().filter(query); } }); } } ... // automatically triggered when setChanged() and notifyObservers() are called public void update(Observable obs, Object obj) { if (obs instanceof FilterManager) { String result = ((FilterManager) obs).getQuery(); // retrieve the search value if (listAdapter != null) { listUpdated = true; // set the update checking value listAdapter.getFilter().filter(result); // filter the list (with #2) } } } }

#4. Conclusión:

Esto funciona bien, las listas en todos los fragmentos anidados se actualizan según lo esperado por una sola búsqueda. Sin embargo, hay un incoveniente en mi código anterior que debe tener en cuenta:

  • (vea las mejoras a continuación) No puedo llamar al objeto general Fragment y agregarlo como un observador. De hecho, tengo que lanzar e iniciar con la clase de fragmento específica (aquí NestedFragment ); Puede haber una solución simple, pero no la encontré por ahora.

A pesar de esto, tengo el comportamiento correcto y, creo, podría ser un buen patrón manteniendo un widget de búsqueda en la parte superior, en actividad. Entonces, con esta solución, podría obtener una pista, una dirección correcta, para lograr lo que desea. Espero que lo disfruten.

# 5 Mejoras (editar):

  • (ver *) Puede agregar observadores manteniendo una extensión de clase Fragment global en todos los fragmentos anidados. Así es como yo instalo mis fragmentos al ViewPager :

    @Override public Fragment getItem(int index) { Fragment frag = null; switch (index) { case 0: frag = new FirstNestedFragment(); break; case 1: frag = new SecondFragment(); break; ... } return frag; } @Override public Object instantiateItem(ViewGroup container, int position) { ObserverFragment fragment = (ObserverFragment) super.instantiateItem(container, position); filterManager.addObserver(fragment); // add the observer return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { filterManager.deleteObserver((ObserverFragment) object); // delete the observer super.destroyItem(container, position, object); }

    Al crear la clase ObserverFragment la siguiente manera:

    public class ObserverFragment extends Fragment implements Observer { public void update(Observable obs, Object obj) { /* do nothing here */ } }

    Y luego, extendiendo y anulando update() en los fragmentos anidados:

    public class FirstNestedFragment extends ObserverFragment { @Override public void update(Observable obs, Object obj) { } }

Aquí, tengo una toolbar de toolbar en una Activity que contiene un SearchView . Y esa actividad tiene múltiples fragmentos. Un fragmento principal de ellos tiene 10 fragmentos más dentro de sí mismo. Los 10 fragmentos muestran datos en vistas de lista. Ahora estoy tratando de filtrar todas las listas de fragmentos por MainActivity de MainActivity . Pero nunca filtra la lista de cada fragmento. Ahora te muestro cómo lo implementé todo.

MainActivity.java

public class MainActivity extends AppCompatActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search)); SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); changeSearchViewTextColor(searchView); return true; } }

Fragmento.java

public class CurrencyFragment2 extends android.support.v4.app.Fragment implements SearchView.OnQueryTextListener { @Override public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); if (menuVisible && getActivity() != null) { SharedPreferences pref = getActivity().getPreferences(0); int id = pref.getInt("viewpager_id", 0); if (id == 2) setHasOptionsMenu(true); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.main, menu); // removed to not double the menu items MenuItem item = menu.findItem(R.id.action_search); SearchView sv = new SearchView(((MainActivity) getActivity()).getSupportActionBar().getThemedContext()); changeSearchViewTextColor(sv); MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); MenuItemCompat.setActionView(item, sv); sv.setOnQueryTextListener(this); sv.setIconifiedByDefault(false); super.onCreateOptionsMenu(menu, inflater); } private void changeSearchViewTextColor(View view) { if (view != null) { if (view instanceof TextView) { ((TextView) view).setTextColor(Color.WHITE); ((TextView) view).setHintTextColor(Color.WHITE); ((TextView) view).setCursorVisible(true); return; } else if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; for (int i = 0; i < viewGroup.getChildCount(); i++) { changeSearchViewTextColor(viewGroup.getChildAt(i)); } } } } @Override public boolean onQueryTextSubmit(String query) { return true; } @Override public boolean onQueryTextChange(String newText) { if (adapter != null) { adapter.filter2(newText); } return true; }

Método de filtro dentro de la clase Adaptador.

// Filter Class public void filter2(String charText) { charText = charText.toLowerCase(Locale.getDefault()); items.clear(); if (charText.length() == 0) { items.addAll(arraylist); } else { for (EquityDetails wp : arraylist) { if (wp.getExpert_title().toLowerCase(Locale.getDefault()).contains(charText)) { items.add(wp); } } } notifyDataSetChanged(); }


Solo para entender mejor la pregunta. 1. Tiene una vista de búsqueda en la Barra de acción 2. Tiene varios Fragmentos (de su Asesor de imagen / TopAdvisors ...) 3. Dentro de cada uno de estos hay múltiples Fragmentos para cada pestaña (ejemplo de equidad ... etc) 4 Desea que todas las vistas de lista en los fragmentos filtren sus datos en función del contenido de búsqueda

¿¿Derecha??

En su implementación actual, ¿cuál es el estado? ¿Se filtra la vista de lista que se muestra actualmente?

¿O incluso eso no está funcionando? A continuación, debe comprobar que notifydatasetChanged se está propagando correctamente al adaptador. Lo que significa que debería estar en el propio UIThread.

Para las actualizaciones de todos los fragmentos, es decir, una vez que no estaban en la pantalla cuando estaba escribiendo el texto de búsqueda, debe considerar el ciclo de vida del Fragmento e incluir el código enResume del fragmento para asegurarse de que la lista se filtre antes de que se use para inicializar el adaptador . Esto es para el escenario en el que ya ha escrito el texto de búsqueda y ahora se está moviendo entre pestañas / fragmentos. Por lo tanto, los fragmentos se encienden y se apagan en la pantalla, por lo tanto, la necesidad de onResume.

Actualización: incluya el cuadro de búsqueda de verificación para el texto, y si tiene algo que llama adapter.filter2 () en onResume, porque eso es básicamente lo que está filtrando su lista. .


  • el problema es que, cuando inicia un fragmento, "se hace cargo" de searchView de la actividad y todo lo que escribe DESPUÉS de que se inicie este fragmento invoca al oyente onQueryTextChange de JUST THIS fragmento ... Por lo tanto, la búsqueda no se realiza en ningún otro fragmento ..
  • Desea que sus fragmentos comprueben la última consulta de búsqueda (publicada por cualquier otro fragmento) cuando se inicien y realicen una búsqueda en sí misma para esa consulta. Además, cualquier cambio en la consulta de búsqueda también debe publicarse en la vista de búsqueda de la actividad para que otros fragmentos puedan leerlo cuando se inician.

Supongo que su viewpager / pestañas se implementan de manera que cada vez que cambia los fragmentos visibles, se destruyen.

Realiza los siguientes cambios (lee los comentarios)

public class MainActivity extends AppCompatActivity { final SearchView searchView; //make searchView a class variable/field @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search)); SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); changeSearchViewTextColor(searchView); return true; } }

Ahora, en todos tus fragmentos haz esto.

@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.main, menu); // removed to not double the menu items MenuItem item = menu.findItem(R.id.action_search); SearchView sv = new SearchView(((MainActivity) getActivity()).getSupportActionBar().getThemedContext()); changeSearchViewTextColor(sv); MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); MenuItemCompat.setActionView(item, sv); sv.setOnQueryTextListener(this); sv.setIconifiedByDefault(false); sv.setQuery(((MainActivity)getActivity()).searchView.getQuery()); // set the main activity''s search view query in the fragment''s search view super.onCreateOptionsMenu(menu, inflater); }

Además, en su SearchView.OnQueryTextListener en los fragmentos, haga esto

SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { search(query); // this is your usual adapter filtering stuff, which you are already doing ((MainActivity)getActivity()).searchView.setQuery(query);//sync activity''s searchview query with fragment''s searchview query, so other fragments can read from the activity''s search query when they launch. return false; } @Override public boolean onQueryTextChange(String newText) { search(newText);// this is your usual adapter filtering stuff, which you are already doing ((MainActivity)getActivity()).searchView.setQuery(query);//sync activity''s searchview query with fragment''s searchview query return false; } });

Espero que tengas la idea general

  • Tiene diferentes instancias de vista de búsqueda en cada uno de sus fragmentos y la actividad
  • necesitas sincronizar las consultas de búsqueda entre cada una de ellas, hacer que cada una de ellas sepa lo que tienen las otras instancias.

Sin embargo, no recomiendo este diseño, lo que preferiría hacer es

  • simplemente use la vista de búsqueda de la actividad y en el servicio de escucha onQueryTextChange de la vista de búsqueda de la actividad.
  • notificaría a todos los fragmentos vivos de onQueryTextChange en la actividad (los fragmentos se registrarían y anularían el registro de los oyentes de QueryTextChange con la actividad en su onResume & onPause de los fragmentos respectivos). [Nota al margen: este patrón de diseño se llama patrón Observer ]