SearchView ne filtre pas dans chaque onglet enfant de TabLayout

Ici, j'ai une toolbar dans une Activity qui contient un SearchView . Et cette activité comporte de multiples fragments. Un fragment principal d'entre eux comporte 10 fragments supplémentaires à l'intérieur même. Les 10 fragments affichent des données dans les listes. Maintenant, j'essaie de filtrer toutes les listes de fragments par SearchView de MainActivity . Mais il ne filtre jamais la liste de chaque fragment. Maintenant, je vous montre comment j'ai tout mis en œuvre.

MainActivity.java

  • Comment pouvez-vous gérer le rejet d'un DialogFragment (compatibilité lib) à la fin d'un AsyncTask
  • Comment convertir une chaîne en long
  • Attendez que les threads soient terminés avant de continuer
  • Vérifiez BitmapDescriptor pour null
  • Eclipse / Java - Valeurs dans R.string. * Return int?
  • Pourquoi Gradle a-t-il besoin d'un fichier settings.gradle?
  •  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; } } 

    Fragment.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éthode de filtrage dans la classe d'adaptateur.

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

    3 Solutions collect form web for “SearchView ne filtre pas dans chaque onglet enfant de TabLayout”

    Vous pouvez gérer le filtre sur une liste imbriquée en utilisant un modèle Observable / Observer , cela mettra à jour chaque liste imbriquée d'un parent Observable . J'ai réparé tous les problèmes et cela fonctionne bien maintenant pour atteindre le bon comportement.

    Par conséquent, voici ce que j'ai fait pour y parvenir:

    1. Utilisation d'une recherche parentale dans Activity
    2. (Facultatif) Créez une classe de Filter ( android.widget.Filter ) dans la liste imbriquée Adapter
    3. Ensuite, en utilisant un motif Observable / Observer pour le Fragment imbriqué avec l' Activity

    Contexte: Lorsque j'ai essayé votre code, j'ai eu trois problèmes:

    • Je ne peux pas faire une recherche en utilisant ActionBar: onQueryTextChange semble ne jamais être appelé dans Fragment s. Lorsque je tape sur l'icône de recherche, il me semble que SearchView (edittext, icône, etc.) n'est pas associé au widget de recherche (mais joint au widget de l'activité).
    • Je ne peux pas exécuter le filter2 méthode filter2 : je veux dire, lorsque j'ai résolu le point précédent, cette méthode ne fonctionne pas. En effet, je dois jouer avec une classe personnalisée étendue par Filter et ses deux méthodes: performFiltering et publishResults . Sans ça, j'ai un écran vierge lorsque je tape un mot dans la barre de recherche. Cependant, ce pourrait être seulement mon code et peut-être filter2() fonctionne parfaitement pour vous …
    • Je ne peux pas avoir une recherche persistante entre fragments: pour chaque fragment d'enfant, un nouveau SearchView est créé. Il me semble que vous appelez à plusieurs reprises cette ligne SearchView sv = new SearchView(...); Dans un fragment imbriqué. Donc chaque fois que je passe au fragment suivant, la vue de recherche étendue supprime sa valeur de texte précédente.

    Quoi qu'il en soit, après quelques recherches, j'ai trouvé cette réponse sur SO sur la mise en œuvre d'un fragment de recherche . Presque le même code que le vôtre, sauf que vous "doublez" le code de menu d'options dans l'activité parentale et en fragments. Vous ne devriez pas le faire – Je pense que c'est la cause de mon premier problème dans les points précédents.
    En outre, le modèle utilisé dans le lien de la réponse (une recherche dans un fragment) peut ne pas être adapté à la vôtre (une recherche de fragments multiples). Vous devriez appeler un SearchView dans l' Activity pour tous les Fragment imbriqués.


    Solution: C'est ainsi que je l'ai réussi:

    # 1 Utilisation d'une SearchView :

    Cela évitera les fonctions en double et permettra à l'activité parentale de superviser tous ses enfants. De plus, cela évitera votre icône de duplication dans le menu.
    C'est la principale classe d' Activity parentale:

     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 (facultatif) Créer un widget de Filter :

    Comme je l'ai déjà dit, je ne peux pas le faire fonctionner avec filter2() , donc je crée une classe de Filter comme n'importe quel exemple sur le Web.
    Il ressemble rapidement, dans l'adaptateur de fragment imbriqué, comme suit:

     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 Utilisation d'un motif Observable / Observer :

    L'activité – avec la recherche – est l'objet Observable et les fragments imbriqués sont les Observer ( voir le modèle Observer ). Fondamentalement, lorsque l' onQueryTextChange sera appelé, il déclenchera la méthode update() dans les observateurs existants.
    Voici la déclaration en parent Activity :

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

    C'est la classe Observable qui écoutera et "passera" les données mises à jour:

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

    Pour ajouter les fragments de l'observateur pour écouter la valeur de la recherche, je le fais quand ils sont initialisés dans FragmentStatePagerAdapter .
    Donc, dans le fragment parent, je crée les onglets de contenu en passant le 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 ); } 

    L'adaptateur ajoute l'observateur au parent observable et l'enlève lorsque les fragments d'enfant sont détruits.
    Voici le ViewPagerAdapter du fragment parent:

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

    Enfin, lorsque filterManager.setQuery() dans l'activité s'appelle avec onQueryTextChange() , cela sera reçu dans le fragment imbriqué dans la méthode update() qui implémentent Observer .
    Il s'agit des fragments imbriqués avec ListView à filtrer:

     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. Conclusion:

    Cela fonctionne bien, les listes dans tous les fragments imbriqués sont mises à jour comme prévu par une seule vue de recherche. Cependant, il existe un code incovenient dans mon code ci-dessus que vous devez savoir:

    • (Voir les améliorations ci-dessous) Je ne peux pas appeler l'objet général Fragment et l'ajouter à un observateur. En effet, je dois lancer et initer avec la classe de fragment spécifique (ici NestedFragment ); Il pourrait y avoir une solution simple, mais je ne l'ai pas trouvé pour l'instant.

    Malgré cela, j'ai le bon comportement et – je pense – ce pourrait être un bon modèle en gardant un widget de recherche au sommet, en activité. Donc, avec cette solution, vous pourriez obtenir un indice, une bonne direction, pour atteindre ce que vous voulez. J'espère que vous apprécierez.


    # 5 Améliorations (modifier):

    • (Voir *) Vous pouvez ajouter les observateurs en maintenant une extension de classe Fragment globale sur tous les fragments imbriqués. Voici comment je ViewPager instanciation de mes fragments dans le 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); } 

      En créant la classe ObserverFragment comme suit:

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

      Ensuite, en étendant et en suralimentant la update() dans les fragments imbriqués:

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

    Juste pour mieux comprendre la question. 1. Vous avez une vue de recherche dans la barre d'action 2. Vous avez plusieurs fragments (à partir de votre image Advisory / TopAdvisors …) 3. Dans chacun de ces éléments, il existe plusieurs fragments pour chaque onglet (exemples d'équité … etc.) 4 . Vous voulez que toutes les vues de liste dans les fragments filtrent leurs données en fonction du contenu de la recherche

    Droite??

    Dans votre mise en œuvre actuelle, quel est le statut? Est-ce que la vue en liste qui s'affiche actuellement est filtrée?

    Ou même cela ne fonctionne pas? Ensuite, vous devez vérifier notifydatasetChanged se propage correctement à l'adaptateur. Cela signifie qu'il devrait être sur l'UIT se lisser.

    Pour les mises à jour de tous les fragments, c'est-à-dire une fois qui n'étaient pas à l'écran lorsque vous tapez le texte de recherche, vous devez tenir compte du cycle de vie des fragments et inclure un code dans onResume du fragment pour s'assurer que la liste est filtrée avant d'être utilisée pour initialiser l'adaptateur . Il s'agit d'un scénario où vous avez tapé du texte de recherche déjà et vous déplacez maintenant entre les onglets / fragments. Alors les fragments allaient et s'éteignaient, donc le besoin de onResume.

    Mise à jour: inclure la case à cocher de recherche pour le texte, et s'il a quelque chose appelant adapter.filter2 () dans onResume, car c'est essentiellement ce qui filtre votre liste. .

    • Le problème est – lorsque vous lancez un fragment, il "reprend" la recherche de l'activité et ce que vous tapez APRÈS que ce fragment est lancé, invoque l'auditeur onQueryTextChange de JUST THIS fragment … Par conséquent, la recherche n'a pas lieu dans aucun autre fragment.
    • Vous voulez que vos fragments vérifient la dernière requête de recherche (publiée par un autre fragment) lorsqu'ils sont lancés et effectuez une recherche en soi pour cette requête. En outre, toute modification de la requête de recherche doit également être affichée dans la vue de recherche de l'activité afin que d'autres fragments puissent le lire lors de leur lancement.

    Je présume que votre viewpager / onglets sont implémentés de telle sorte que chaque fois que vous changez les fragments visibles, ils sont détruits.

    Effectuez les modifications suivantes (lisez les commentaires)

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

    Maintenant, dans tous vos fragments, faites ceci –

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

    De plus, dans votre SearchView.OnQueryTextListener dans les fragments, faites ceci

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

    J'espère que vous avez l'idée générale

    • Vous avez différentes instances de recherche dans chacun de vos fragments et l'activité
    • Vous devez synchroniser les requêtes de recherche parmi chacune d'elles, faire en sorte qu'elles soient conscientes de ce que les autres instances ont …

    Je ne recommande pas ce design, mais ce que je préfère ne serait pas

    • Utilisez simplement la vue de recherche de l'activité et dans la liste de recherche de l'auditeur onQueryTextChange de l'activité.
    • Je notifierais tous les fragments qui sont en vie de onQueryTextChange dans l'activité (les fragments s'inscriraient et désenregistrent les auditeurs onQueryTextChange avec l'activité dans leur OnResume & onPause des fragments respectifs). [Note latérale – Ce motif de conception s'appelle Pattern Observer ]
    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.