解决:SearchView不会在TabLayout的每个子Tab中进行过滤 [英] Solved: SearchView doesn't filter in each child Tab of TabLayout

查看:86
本文介绍了解决:SearchView不会在TabLayout的每个子Tab中进行过滤的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在这里,我在Activity中有一个toolbar,其中包含SearchView.该活动具有多个片段.其中一个主要片段本身内部又有10个片段.所有10个片段都在列表视图中显示数据.现在,我尝试按MainActivitySearchView过滤所有片段列表.但是它永远不会过滤每个片段的列表.现在,我向您展示如何实现这一切.

Here, I have a toolbar in an Activity which contains a SearchView. And that activity has multiple fragments. One main fragment out of them have 10 more fragments inside itself. All 10 fragments are showing data in listviews. Now I'm trying to filter all the lists of fragments by SearchView of MainActivity. But it never filters list of each fragment. Now I show you how I implemented it all.

MainActivity.java

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

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 method inside Adapter class.

// 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模式来管理嵌套列表上的过滤器,这将从一个可观察的父级.我修复了所有麻烦,现在它可以很好地实现正确的行为.

You can manage the filter on nested list by using an Observable/Observer pattern, this will update each nested list from one Observable parent. I fixed all troubles and it works well now to achieve the right behaviour.

因此,这是我要做的事情:

Therefore, here's what I did to achieve it:

  1. Activity中使用一个父级SearchView
  2. (可选)在嵌套列表Adapter
  3. 中创建一个Filter类( android.widget.Filter )
  4. 然后,将Observable/Observer模式用于带有Activity的嵌套Fragment
  1. Using one parent SearchView in Activity
  2. (optional) Create a Filter class (android.widget.Filter) in nested list Adapter
  3. Then, using an Observable/Observer pattern for nested Fragment with Activity


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


Background: When I tried your code, I had three problems:

  • 我无法使用ActionBar进行搜索:onQueryTextChange似乎从未在Fragment s中调用.当我点击搜索图标时,在我看来,SearchView(编辑文本,图标等)未随搜索窗口小部件附加(而是附加到活动的窗口小部件).
  • 我无法运行自定义方法filter2:我的意思是,当我解决上一点时,该方法不起作用.确实,我必须使用由Filter扩展的自定义类及其两个方法:performFilteringpublishResults.没有它,当我在搜索栏中点击一个单词时,我将得到一个空白屏幕.但是,这可能只是我的代码,也许filter2()非常适合您...
  • 我无法在片段之间进行持久搜索:对于每个子片段,都会创建一个新的SearchView.在我看来,您反复在嵌套片段中将此行称为SearchView sv = new SearchView(...);.因此,每次切换到下一个片段时,展开的searchview都会删除其先前的文本值.
  • I cannot do a search using the ActionBar: onQueryTextChange seems to be never called in Fragments. When I tap on search icon, it seems to me that SearchView (edittext, icon, etc) is not attached with the search widget (but attached to the activity's widget).
  • I cannot run the custom method filter2: I mean, when I resolved the previous point, this method doesn't work. Indeed, I have to play with custom class extending by Filter and its two methods: performFiltering and publishResults. Without it, I got a blank screen when I tap a word in search bar. However, this could be only my code and maybe filter2() works perfectly for you...
  • I cannot have a persistent search between fragments: for each child fragment a new SearchView is created. It seems to me that you repeatedly call this line SearchView sv = new SearchView(...); in nested fragment. So each time I switch to the next fragment, the expanded searchview removes its previous text value.

无论如何,经过一些研究,我发现关于实现此答案/stackoverflow.com/questions/7230893/android-search-with-fragments>搜索片段.除了您在父活动和片段中复制"选项菜单代码外,几乎与您的代码相同.您不应该这样做-我认为这是我先前提到的第一个问题的原因. 此外,答案链接中使用的模式(在一个片段中进行一次搜索)可能不适合您的模式(在一个片段中进行一次搜索).您应该为所有嵌套的Fragment调用父级Activity中的一个SearchView.

Anyway, after some researches, I found this answer on SO about implementing a Search fragment. Almost the same code as yours, except that you "duplicate" the options menu code in parent activity and in fragments. You shouldn't do it - I think it's the cause of my first problem in previous points.
Besides, the pattern used in the answer's link (one search in one fragment) might not be adapted to yours (one search for multiple fragments). You should call one SearchView in the parent Activity for all nested Fragment.

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

Solution: This is how I managed it:

#1使用父级SearchView:

#1 Using a parent SearchView:

它将避免重复的功能,并让父级活动监督其所有子级.此外,这将避免菜单中出现重复图标.
这是主要的父Activity类:

It will avoid duplicate functions and let the parent activity supervise all its children. Futhermore, this will avoid your duplication icon in the menu.
This is the main parent Activity class:

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小部件:

#2 (optional) Create a Filter widget:

就像我之前说过的,我无法使其与filter2()一起使用,因此我创建了Filter类作为网络上的任何示例.
很快,在嵌套片段的适配器中,如下所示:

Like I said previously, I cannot get it work with filter2(), so I create a Filter class as any example on the web.
It quickly looks like, in the adapter of nested fragment, as follows:

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模式:

#3 Using an Observable/Observer pattern:

具有searchview的活动是Observable对象,而嵌套的片段是Observer s().基本上,当onQueryTextChange将被调用时,它将在现有的观察者中触发update()方法.
这是父Activity中的声明:

The activity - with the searchview - is the Observable object and the nested fragments are the Observers (see Observer pattern). Basically, when the onQueryTextChange will be called, it will trigger the update() method in the existant observers.
Here's the declaration in 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;
}

这是Observable类,它将侦听并传递"更新的数据:

This is the Observable class which will listen and "pass" the updated data:

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:

In order to add the observer fragments to listen the searchview value, I do it when they are initialized in the FragmentStatePagerAdapter.
So in the parent fragment, I create the content tabs by passing the 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:

The adapter will add the observer to the parent observable and remove it when the child fragments are destroyed.
Here's the ViewPagerAdapter of parent fragment:

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进行过滤的嵌套片段:

Finally, when filterManager.setQuery() in activity is called with onQueryTextChange(), this will be received in nested fragment in update() method which are implementing Observer.
This is the nested fragments with the ListView to filter:

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结论:

这很好用,所有嵌套片段中的列表仅通过一个searchview就可以按预期进行更新.但是,在我上面的代码中有一个不便之处,您应注意:

This works well, the lists in all nested fragments are updated as expected by just one searchview. However, there is an incovenient in my above code that you should be aware of:

  • (请参见下面的改进) 我无法调用Fragment常规对象并将其添加为观察者.确实,我必须使用特定的片段类(此处为NestedFragment)进行强制转换和初始化.可能有一个简单的解决方案,但我暂时找不到.
  • (see improvements below) I cannot call Fragment general object and add it to being an observer. Indeed, I have to cast and init with the specific fragment class (here NestedFragment); there might be a simple solution, but I didn't find it for now.

尽管如此,我还是得到了正确的行为,并且-我认为-通过将一个搜索小部件保持在活动顶部是一种很好的模式.因此,使用此解决方案,您可以获得正确的方向线索,以实现所需的目标.希望您会喜欢.

Despite this, I get the right behaviour and - I think - it might be a good pattern by keeping one search widget at the top, in activity. So with this solution, you could get a clue, a right direction, to achieve what you want. I hope you'll enjoy.

#5改进(编辑):

  • (请参阅*).您可以通过在所有嵌套片段上保留全局Fragment类扩展名来添加观察者.这就是我将片段实例化到ViewPager的方法:

  • (see *) You can add the observers by keeping a global Fragment class extension on all nested fragments. This how I instantiate my fragments to the 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():

And then, by extending and overriding update() in the nested fragments:

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

这篇关于解决:SearchView不会在TabLayout的每个子Tab中进行过滤的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆