SearchView не фильтрует в каждом дочернем элементе Tab TabLayout

Здесь у меня есть toolbar в Activity которая содержит SearchView . И эта деятельность имеет несколько фрагментов. Один из основных фрагментов из них имеет еще 10 фрагментов внутри себя. Все 10 фрагментов отображают данные в списках. Теперь я пытаюсь отфильтровать все списки фрагментов с помощью SearchView из MainActivity . Но он никогда не фильтрует список каждого фрагмента. Теперь я покажу вам, как я реализовал все это.

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

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

Метод фильтра внутри класса адаптера.

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

Solutions Collecting From Web of "SearchView не фильтрует в каждом дочернем элементе Tab TabLayout"

Вы можете управлять фильтром по вложенному списку с помощью шаблона Observable / Observer , это обновит каждый вложенный список из одного родителя Observable . Я исправил все проблемы, и теперь он работает хорошо, чтобы добиться правильного поведения.

Поэтому вот что я сделал для этого:

  1. Использование одного родителя SearchView в SearchView
  2. (Необязательно) Создайте класс Filter ( android.widget.Filter ) во вложенном списке Adapter
  3. Затем, используя шаблон Observable / Observer для вложенного Fragment с Activity

Предыстория: Когда я пробовал свой код, у меня было три проблемы:

  • Я не могу выполнить поиск с помощью ActionBar: onQueryTextChange похоже, никогда не onQueryTextChange в Fragment s. Когда я нажимаю на значок поиска, мне кажется, что SearchView (edittext, значок и т. Д.) Не привязан к виджету поиска (но прикреплен к виджету активности).
  • Я не могу запустить настраиваемый метод filter2 : я имею в виду, когда я решил предыдущую точку, этот метод не работает. В самом деле, я должен играть с пользовательским классом, распространяющимся по Filter и его двум методам: performFiltering и publishResults . Без этого я получил пустой экран, когда я нажимаю слово в строке поиска. Однако это может быть только мой код и, возможно, filter2() отлично работает для вас …
  • Я не могу иметь постоянный поиск между фрагментами: для каждого дочернего фрагмента создается новый SearchView . Мне кажется, что вы повторно вызываете эту строку SearchView sv = new SearchView(...); Во вложенном фрагменте. Поэтому каждый раз, когда я переключаюсь на следующий фрагмент, расширенное searchview удаляет его предыдущее текстовое значение.

В любом случае, после некоторых исследований, я нашел этот ответ на SO о реализации фрагмента поиска . Почти тот же код, что и ваш, за исключением того, что вы «дублируете» код меню параметров в родительской активности и фрагментах. Вы не должны этого делать – я думаю, что это причина моей первой проблемы в предыдущих пунктах.
Кроме того, шаблон, используемый в ссылке ответа (один поиск в одном фрагменте), не может быть адаптирован к вашему (один поиск нескольких фрагментов). Вы должны вызвать один SearchView в родительской Activity для всех вложенных Fragment .


Решение: Вот как мне это удалось:

# 1 Использование родительского SearchView :

Это позволит избежать дублирования функций и позволить родительской деятельности контролировать всех своих детей. Более того, это позволит избежать вашего символа дублирования в меню.
Это основной родительский класс Activity :

 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 (необязательно) Создайте виджет Filter :

Как я уже говорил ранее, я не могу заставить его работать с filter2() , поэтому я создаю класс Filter как любой пример в Интернете.
Он быстро выглядит в адаптере вложенного фрагмента следующим образом:

 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 Использование шаблона Observable / Observer :

Активность – с помощью searchview – это объект Observable а вложенные фрагменты – Observer s ( см. Шаблон Observer ). В основном, когда onQueryTextChange , он вызывает метод update() в существующих наблюдателях.
Вот объявление в родительской 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; } 

Это класс Observable который будет прослушивать и «передавать» обновленные данные:

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

Чтобы добавить фрагменты наблюдателя для прослушивания значения searchview, я делаю это, когда они инициализируются в FragmentStatePagerAdapter .
Поэтому в родительском фрагменте я создаю вкладки содержимого, передавая 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 ); } 

Адаптер добавит наблюдателя к наблюдаемому родительскому объекту и удалит его, когда дочерние фрагменты будут уничтожены.
Вот ViewPagerAdapter родительского фрагмента:

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

Наконец, когда filterManager.setQuery() в активности вызывается с помощью onQueryTextChange() , это будет получено во вложенном фрагменте в методе update() который реализует Observer .
Это вложенные фрагменты с фильтром ListView для фильтрации:

 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. Вывод:

Это хорошо работает, списки во всех вложенных фрагментах обновляются, как и ожидалось, только одним поиском. Тем не менее, в моем вышеприведенном коде есть несовместимый способ, о котором вы должны знать:

  • (См. Улучшения ниже) Я не могу вызвать общий объект Fragment и добавить его в качестве наблюдателя. В самом деле, я должен использовать и инициализировать определенный класс фрагментов (здесь NestedFragment ); Может быть простое решение, но пока я его не нашел.

Несмотря на это, я получаю правильное поведение, и, я думаю, это может быть хорошей моделью, сохраняя один виджет поиска сверху, в действии. Таким образом, с помощью этого решения вы можете получить ключ, правильное направление, чтобы достичь того, чего хотите. Надеюсь, вам понравится.


# 5 Усовершенствования (изменить):

  • (См. *) Вы можете добавить наблюдателей, сохранив глобальное расширение класса Fragment на всех вложенных фрагментах. Это как я ViewPager свои фрагменты в 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); } 

    Создав класс ObserverFragment следующим образом:

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

    А затем, расширяя и переопределяя update() во вложенных фрагментах:

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

Просто лучше понять вопрос. 1. У вас есть поиск в панели действий 2. У вас есть несколько фрагментов (из вашего изображения Консультант / TopAdvisors …) 3. В каждом из них есть несколько фрагментов для каждой вкладки (пример Equity … и т. Д.) 4 Вы хотите, чтобы все виды списков в фрагментах отфильтровывали свои данные на основе контента для поиска

Правильно??

В вашей текущей реализации каков статус? Отображается ли просмотр списка, который отображается в текущий момент?

Или даже это не работает? Затем вам нужно проверить notifydatasetChanged правильно распространяется на адаптер. Это значит, что это должно быть на самом UIThread.

Для обновлений для всех фрагментов, означающих один раз, который не был на экране при вводе текста поиска, вам необходимо рассмотреть жизненный цикл фрагмента и включить код в onResume фрагмента, чтобы убедиться, что список фильтруется до того, как он используется для инициализации адаптера , Это для сценария, в котором вы уже ввели текст поиска и теперь перемещаетесь между вкладками / фрагментами. Таким образом, фрагменты будут отображаться и выходить за пределы экрана, поэтому необходимо использовать onResume.

Обновление. Включите проверку окна поиска для текста, и если у него есть что-то, вызывающее adapter.filter2 () в onResume, потому что это в основном то, что фильтрует ваш список. ,

  • Проблема в том, что когда вы запускаете фрагмент, он «берет на себя» поиск SearchView и все, что вы набираете ПОСЛЕ того, как этот фрагмент запускается, вызывает прослушиватель onQueryTextChange JUST THIS. Следовательно, поиск не происходит ни в одном другом фрагменте.
  • Вы хотите, чтобы ваши фрагменты проверяли последний поисковый запрос (отправленный любым другим фрагментом) при их запуске и выполняли поиск в нем для этого запроса. Кроме того, любое изменение в поисковом запросе должно быть также опубликовано в виде поиска активности, чтобы другие фрагменты могли прочитать его при запуске.

Я предполагаю, что ваш viewpager / tabs реализован таким образом, что всякий раз, когда вы переключаете видимые фрагменты, они становятся уничтоженными.

Внесите следующие изменения (прочитайте комментарии)

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

Теперь, во всех ваших фрагментах сделайте это –

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

Кроме того, в вашем SearchView.OnQueryTextListener во фрагментах сделайте это

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

Надеюсь, вы получите общую идею

  • У вас есть разные экземпляры вида поиска в каждом из ваших фрагментов и активность
  • Вам необходимо синхронизировать поисковые запросы между каждым из них, чтобы каждый из них знал о том, что есть у других экземпляров.

Я не рекомендую этот дизайн, хотя я бы предпочел сделать это

  • Просто используйте вид поиска активности и в onQueryTextChange слушателя вида поиска активности.
  • Я бы уведомил все фрагменты, которые являются живыми из onQueryTextChange в активности (фрагменты будут регистрировать и отменять регистрацию onQueryTextChange слушателей с активностью в onResume & onPause соответствующих фрагментов). [Сторона примечания. Этот шаблон проектирования называется шаблоном Observer ]