SearchView不会在TabLayout的每个子选项卡中进行过滤

在这里,我在Activity中有一个包含SearchViewtoolbar 。 而且该活动有多个片段。 其中一个主要碎片本身内部还有10个碎片。 所有10个片段都在列表视图中显示数据。 现在我试图通过SearchView of 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; } 

Adapter类中的Filter方法。

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

您可以使用Observable / Observer模式管理嵌套列表上的filter,这将从一个Observable父级更新每个嵌套列表。 我解决了所有麻烦 ,现在它很好地实现了正确的行为。

因此,这就是我为实现它所做的:

  1. Activity使用一个父SearchView
  2. (可选)在嵌套列表Adapter创建一个Filter类( android.widget.Filter
  3. 然后,使用Observable / Observer模式嵌套Fragment with Activity

背景:当我尝试使用您的代码时,我遇到了三个问题:

  • 我无法使用ActionBar进行搜索: onQueryTextChange似乎永远不会在Fragment调用。 当我点击搜索图标时,在我看来SearchView (edittext,icon等)没有附加搜索小部件(但附加到活动的小部件)。
  • 我无法运行自定义方法filter2 :我的意思是,当我解决了前一点时,此方法不起作用。 实际上,我必须使用Filter扩展的自定义类及其两种方法: performFilteringpublishResults 。 没有它,我在搜索栏中点击一个单词时会出现一个空白屏幕。 但是,这可能只是我的代码,也许filter2()适合你…
  • 我不能在片段之间进行持久搜索:对于每个子片段,都会创建一个新的SearchView 。 在我看来你反复调用这行SearchView sv = new SearchView(...); 在嵌套片段中。 因此,每次切换到下一个片段时,扩展的searchview都会删除其先前的文本值。

无论如何,经过一些研究,我在SO上find了关于实现搜索片段的 答案 。 几乎与您的代码相同,只是您在父活动和片段中“复制”选项菜单代码。 你不应该这样做 – 我认为这是我之前的第一个问题的原因。
此外,答案链接中使用的模式(一个片段中的一个搜索)可能不适合您(一个搜索多个片段)。 您应该在父Activity为所有嵌套Fragment调用一个SearchView


解决方案:这是我管理它的方式:

#1使用父SearchView

它将避免重复的function,并让父活动监督其所有子项。 此外,这将避免菜单中的复制图标。
这是主要的父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类作为Web上的任何示例。
在嵌套片段的适配器中,它看起来很快,如下所示:

 private ArrayList originalList; // I used String objects in my tests private ArrayList 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 list = originalList; int count = list.size(); final ArrayList 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) results.values; notifyDataSetChanged(); } } 

#3使用Observable / Observer模式:

使用searchview的活动是Observable对象,嵌套的片段是Observer ( 参见Observer模式 )。 基本上,当调用onQueryTextChange时,它将触发onQueryTextChangeupdate()方法。
这是父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 ); } 

适配器将观察者添加到父observable,并在子片段被销毁时将其删除。
这是父片段的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 )进行NestedFragment和初始化。 可能有一个简单的解决方案,但我现在还没有find它。

尽管如此,我得到了正确的行为 – 我认为 – 通过将一个搜索小部件保留在活动的顶部,这可能是一个很好的模式。 因此,使用此解决方案,您可以获得线索,正确的方向,以实现您想要的。 我希望你会喜欢。


#5改进(编辑):

  • (请参阅*)您可以通过在所有嵌套片段上保留全局Fragment类扩展来添加观察者。 这是我如何将我的片段实例化到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。

更新:包括检查文本的搜索框,以及是否在onResume中调用了adapter.filter2(),因为这基本上是过滤列表的内容。 。

  • 问题是 – 当你启动一个片段时,它“接管”活动的searchView和你输入的这个片段之后输入的任何内容都会调用JUST THIS片段的onQueryTextChange监听器。因此,搜索不会发生在任何其他片段中。
  • 您希望片段在启动时检查最后一个搜索查询(由任何其他片段发布),并为该查询执行搜索。 此外,搜索查询中的任何更改也必须发布到活动的搜索视图中,以便其他片段在启动时可以读取它。

我假设你的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是相应片段的活动)。 [旁注 – 此设计模式称为观察者模式]