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

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

问题描述

在这里,我在包含 SearchViewActivity 中有一个 toolbar.该活动有多个片段.其中的一个主要碎片内部还有 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
  2. 中使用一个父SearchView
  3. (可选)在嵌套列表Adapter
  4. 中创建一个Filter类(android.widget.Filter)
  5. 然后,使用 Observable/Observer 模式嵌套 FragmentActivity
  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 中被调用.当我点击搜索图标时,在我看来 SearchView(编辑文本、图标等)没有附加到搜索小部件(而是附加到活动的小部件).
  • 我无法运行自定义方法 filter2:我的意思是,当我解决上一点时,此方法不起作用.实际上,我必须使用由 Filter 及其两个方法扩展的自定义类:performFilteringpublishResults.没有它,当我在搜索栏中点击一个词时,我会看到一个空白屏幕.但是,这可能只是我的代码,也许 filter2() 非常适合您...
  • 我无法在片段之间进行持久搜索:为每个子片段创建一个新的 SearchView.在我看来,您在嵌套片段中反复调用此行 SearchView sv = new 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.

无论如何,经过一些研究,我在 SO 上找到了关于实施 搜索片段.几乎与您的代码相同,只是您在父活动和片段中复制"了选项菜单代码.你不应该这样做 - 我认为这是我前面几点中的第一个问题的原因.此外,答案链接中使用的模式(一个片段中的一个搜索)可能不适合您的(一个搜索多个片段).您应该在父 Activity 中为所有嵌套的 Fragment 调用一个 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.

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

#1 使用父 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:

带有搜索视图的活动是 Observable 对象,嵌套的片段是 Observers (参见观察者模式).基本上,当 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() 时,这将在 update() 的嵌套片段中接收code> 方法正在实现 Observer.
这是带有要过滤的 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 结论:

这很有效,所有嵌套片段中的列表仅通过一个搜索视图按预期更新.但是,您应该注意我上面的代码中的一个不便之处:

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天全站免登陆