已解决:SearchView 不会在 TabLayout 的每个子选项卡中进行过滤
Posted
技术标签:
【中文标题】已解决:SearchView 不会在 TabLayout 的每个子选项卡中进行过滤【英文标题】:SearchView doesn't filter in each child Tab of TabLayout 【发布时间】:2016-02-04 15:19:35 【问题描述】:在这里,我在 Activity
中有一个 toolbar
,其中包含一个 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;
片段.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 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();
【问题讨论】:
【参考方案1】:您可以通过使用 Observable/Observer 模式来管理嵌套列表上的过滤器,这将从一个 Observable parent 更新每个嵌套列表。我修复了all troubles,现在它可以很好地实现正确的行为。
因此,这是我为实现它所做的:
-
在
Activity
中使用一个父SearchView
(可选)在嵌套列表Adapter
中创建Filter
类(android.widget.Filter)
然后,使用Observable
/Observer
模式嵌套Fragment
和Activity
背景:当我尝试你的代码时,我遇到了三个问题:
我无法使用 ActionBar 进行搜索:onQueryTextChange
似乎从未在 Fragment
s 中调用过。当我点击搜索图标时,在我看来 SearchView
(edittext、icon 等)没有附加到搜索小部件(但附加到活动的小部件)。
我无法运行自定义方法filter2
:我的意思是,当我解决了上一点时,此方法不起作用。实际上,我必须使用由Filter
扩展的自定义类及其两种方法:performFiltering
和publishResults
。没有它,当我在搜索栏中点击一个单词时,我会得到一个空白屏幕。但是,这可能只是我的代码,也许 filter2()
非常适合您...
我无法在片段之间进行持久搜索:为每个子片段创建一个新的SearchView
。在我看来,您在嵌套片段中反复调用此行SearchView sv = new SearchView(...);
。所以每次我切换到下一个片段时,展开的搜索视图都会删除它之前的文本值。
无论如何,经过一些研究,我在 SO 上找到了this answer 关于实现Search fragment。与您的代码几乎相同,除了您在父活动和片段中“复制”选项菜单代码。你不应该这样做 - 我认为这是我前面提到的第一个问题的原因。
此外,答案链接中使用的模式(一个片段中的一次搜索)可能不适合您的模式(一次搜索多个片段)。对于所有嵌套的Fragment
,您应该在父级Activity
中调用一个SearchView
。
解决方案:我是这样处理的:
#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
模式:
带有搜索视图的活动是Observable
对象,嵌套片段是Observer
s (see Observer pattern)。基本上,当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
);
适配器将add the observer 到父可观察对象,并在子片段被销毁时将其删除。
这是父片段的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);
最后,当使用onQueryTextChange()
调用活动中的filterManager.setQuery()
时,这将在实现Observer
的update()
方法的嵌套片段中接收。
这是用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
:
@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)
【讨论】:
抱歉迟到了。我想让你知道我在上周三解决了我的问题,我不认为我的代码对你不起作用,因为它一直在工作,但问题与当前片段的可见性有关。 没问题@AnshulTyagi。我很高兴你解决了这个问题,我确信那是我的代码不起作用,或者至少是我的实现。然而,当我尝试时,我不得不处理它,所以我在过滤器附近写了“可选”,因为我认为这对其他人有帮助。即使您以其他方式解决了问题,我也很乐意为它寻找解决方案。这很有趣,也许你可以稍后再试一试;) 我没有尝试你的答案,但我认为你应该得到这份赏金,因为你为我的问题所做的研究和辛勤工作。祝你好运?【参考方案2】: 问题是 - 当您启动一个片段时,它会“接管”活动的 searchView 并且无论您在启动此片段后键入什么都会调用 JUST THIS 片段的 onQueryTextChange 侦听器。因此,搜索不会发生在任何其他片段.. 您希望您的片段在启动时检查最后一个搜索查询(由任何其他片段发布)并针对该查询执行自身搜索。此外,搜索查询中的任何更改也必须发布到 Activity 的搜索视图,以便其他 Fragment 在启动时可以读取它。我假设您的 viewpager/tabs 的实现方式是,每当您切换可见片段时,它们都会被破坏。
进行以下更改(阅读 cmets)
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 通知所有处于活动状态的片段(片段将在各自片段的 onResume 和 onPause 中注册和取消注册 onQueryTextChange 侦听器)。 [旁注 - 这种设计模式称为Observer 模式]【讨论】:
它将如何过滤每个列表视图?是的,能见度是主要问题。顺便说一句,您没有在这里谈论 setHasOptionsMenu(true)。 - 问题是 - 当你启动一个片段时,它会“接管”活动的 searchView 并且你在这个片段启动之后输入的任何内容都会调用 JUST THIS 片段的 onQueryTextChange 监听器。因此,搜索不会在任何其他片段中发生.. - 您希望片段在启动时检查最后一个搜索查询(由任何其他片段发布)并针对该查询执行自身搜索。此外,搜索查询中的任何更改也必须发布到活动的搜索视图,以便其他片段在启动时可以读取它。 好吧,我也试试这个,但是 setHasOptionsMenu 呢? 怎么样?您需要调用 setHasOptionsMenu(true) 以便调用片段的 onCreateOptionsMenu。所以,是的,你必须从你的 onCreate 调用 setHasOptionsMenu(在片段中) 是的,我在 setmenuVisibility 中调用它来检查当前的Fragment
是否可见并且它正在变为 true。【参考方案3】:
只是为了更好地理解这个问题。 1.您在操作栏中有一个搜索视图 2.您有多个片段(来自您的图像咨询/TopAdvisors...) 3.在每个选项卡中,每个选项卡都有多个片段(例如Equity ...等) 4. 您希望片段中的所有列表视图根据搜索内容过滤其数据
对吗??
在您当前的实施中处于什么状态?当前显示的列表视图是否被过滤?
或者即使这样也行不通?然后您需要检查 notifydatasetChanged 是否正确传播到适配器。这意味着它应该在 UIThread 本身。
对于所有 Fragment 的更新,即当您键入搜索文本时没有出现在屏幕上的更新,您需要考虑 Fragment 生命周期并在 Fragment 的 onResume 中包含代码,以确保列表在用于之前被过滤初始化适配器。这适用于您已经键入搜索文本并且现在正在选项卡/片段之间移动的情况。所以片段会在屏幕上和屏幕外出现,因此需要 onResume。
更新: 包括检查文本的搜索框,以及是否在 onResume 中有调用 adapter.filter2() 的内容,因为这基本上就是过滤列表的内容。 .
【讨论】:
看。有用。但是当我选择第一个选项卡(ADVISORY)时,它会过滤此选项卡的子项。并且在选择下一个选项卡(TOP ADVISORY)后,它会过滤此选项卡子级的所有列表视图,但会停止在前一个选项卡(ADVISORY)中进行过滤。最后,我转向 EXPERTVIEW,它只适用于它而不适用于其他人。我希望你明白我的意思。 为了更清楚,我会写一个场景让我知道 YES/NO 考虑您在“咨询”选项卡上,您进行了搜索,现在一切都适用于这个标签? (是/否)现在您移动到 TopAdvisory 选项卡,并且过滤正在此选项卡的子选项卡中工作? (是/否)但是如果您现在返回咨询选项卡,过滤不再起作用?(是/否)如果您现在返回***咨询选项卡会发生什么?过滤还有效吗? (是/否) 是的,您了解这里当前发生的一切。 请检查您的片段的 onResume 是否正在处理过滤并通知适配器数据集已更改。看来您只在 onCreateView 中执行此操作。这仅在创建片段并且第一次填充视图时调用一次。要在片段切换之间保持过滤活动,您需要确保 onResume 处理逻辑 对不起?我没有在 onCreate 中为它做任何事情。我正在使用 setMenuVisibility 覆盖方法来检查片段现在是否可见或被选中。以上是关于已解决:SearchView 不会在 TabLayout 的每个子选项卡中进行过滤的主要内容,如果未能解决你的问题,请参考以下文章
放置在 Listview 上时,Android Searchview 不会触发