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:
- Usando un
SearchView
padre en laActivity
- (Opcional) Cree una clase de
Filter
( android.widget.Filter ) en la lista anidadaAdapter
- Luego, usando un patrón
Observable
/Observer
para elFragment
anidado conActivity
Antecedentes: cuando probé tu código, tuve tres problemas:
- No puedo hacer una búsqueda utilizando ActionBar: parece que nunca se llama a
onQueryTextChange
enFragment
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 porFilter
y sus dos métodos:performFiltering
ypublishResults
. 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 elfilter2()
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íneaSearchView 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 generalFragment
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 alViewPager
:@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 ]