已解决: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 个片段都在列表视图中显示数据。现在我正在尝试通过SearchViewMainActivity 过滤所有片段列表。但它从不过滤每个片段的列表。现在我向您展示我是如何实现这一切的。

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 模式嵌套FragmentActivity

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

我无法使用 ActionBar 进行搜索:onQueryTextChange 似乎从未在 Fragments 中调用过。当我点击搜索图标时,在我看来 SearchView(edittext、icon 等)没有附加到搜索小部件(但附加到活动的小部件)。 我无法运行自定义方法filter2:我的意思是,当我解决了上一点时,此方法不起作用。实际上,我必须使用由Filter 扩展的自定义类及其两种方法:performFilteringpublishResults。没有它,当我在搜索栏中点击一个单词时,我会得到一个空白屏幕。但是,这可能只是我的代码,也许 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 对象,嵌套片段是Observers (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() 时,这将在实现Observerupdate() 方法的嵌套片段中接收。 这是用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 不会触发

如何检测SearchView是否已扩展?

在操作栏中实现 SearchView

谁能解决这个 ListView onItemClickListener 错误?在 searchView 中也出现错误

重置搜索小部件 (SearchView) 值

Android 使用SearchView控件查询时弹出黑色提示框的解决方法