SearchView 不会在 TabLayout 的每个子 Tab 中进行过滤

SearchView doesn't filter in each child Tab of TabLayout

在这里,我在 Activity 中有一个 toolbar,其中包含一个 SearchView。而 activity 有多个片段。其中一个主要碎片内部还有 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;
}

适配器内部的过滤方法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();
}

只是为了更好地理解问题。 1.您在操作栏中有一个搜索视图 2. 你有多个片段(来自你的图像 Advisory/TopAdvisors...) 3.在每个选项卡中,每个选项卡都有多个片段(例如股票......等) 4. 您希望片段中的所有列表视图都根据搜索内容过滤它们的数据

对吗??

在你目前的实施中是什么状态?当前显示的列表视图是否被过滤?

或者即使这样也行不通?然后您需要检查 notifydatasetChanged 是否正确传播到适配器。这意味着它应该在 UIThread 本身上。

对于所有片段的更新,这意味着当您输入搜索文本时,一旦没有出现在屏幕上,您需要考虑片段生命周期并在片段的 onResume 中包含代码,以确保列表在它出现之前被过滤用于初始化适配器。这适用于您已经键入搜索文本并且现在正在 tabs/fragments 之间移动的情况。所以片段会在屏幕上和屏幕外出现,因此需要 onResume。

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

  • 问题是 - 当您启动片段时,它 "takes over" activity 的 searchView 以及您在启动此片段后键入的任何内容都会调用 JUST THIS 片段的 onQueryTextChange 侦听器。因此, 搜索不会在任何其他片段中发生..
  • 您希望您的片段在启动时检查最后一个搜索查询(由任何其他片段发布),并针对该查询自行执行搜索。此外,搜索查询中的任何更改也必须发布到 activity 的搜索视图,以便其他片段在启动时可以读取它。

我假设您的 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;
            }
        });

希望你能大致了解一下

  • 您的每个片段中都有不同的搜索视图实例,activity
  • 你需要在每个实例之间同步搜索查询,让每个实例都知道其他实例有什么。

虽然我不推荐这种设计,但我更愿意做的是

  • 只需使用 activity 的搜索视图和 activity 搜索视图的 onQueryTextChange 侦听器。
  • i 会从 activity 中的 onQueryTextChange 通知所有活动的片段(片段会在各自片段的 onResume 和 onPause 中使用 activity 注册和注销 onQueryTextChange 侦听器)。 [旁注 - 这种设计模式称为 Observer 模式]

您可以通过 使用 Observable/Observer 模式 来管理嵌套列表上的过滤器,这将从一个 Observable parent. I fixed all troubles 更新每个嵌套列表并且效果很好现在实现正确的行为。

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

  1. Activity
  2. 中使用一个parentSearchView
  3. (可选)在嵌套列表 Adapter
  4. 中创建一个 Filter class (android.widget.Filter)
  5. 然后,使用 Observable/Observer 模式嵌套 FragmentActivity

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

  • 我无法使用 ActionBar 进行搜索:onQueryTextChange 似乎从未在 Fragment 中调用过。当我点击搜索图标时,在我看来 SearchView(编辑文本、图标等)没有附加到搜索小部件(但附加到 activity 的小部件)。
  • 我不能运行自定义方法filter2: 我的意思是,当我解决前一点时,这个方法不起作用。实际上,我必须使用通过 Filter 扩展的自定义 class 及其两种方法:performFilteringpublishResults。没有它,当我在搜索栏中点击一个词时,我会得到一个空白屏幕。但是,这可能只是我的代码,也许 filter2() 非常适合您...
  • 我无法在片段之间进行持久搜索:为每个 child 片段创建一个新的 SearchView。在我看来,您在嵌套片段中反复调用此行 SearchView sv = new SearchView(...); 。因此,每次我切换到下一个片段时,展开的搜索视图都会删除其先前的文本值。

总之,经过一番研究,我找到了this answer on SO about implementing a Search fragment。几乎与您的代码相同,只是您 "duplicate" parent activity 和片段中的选项菜单代码。你不应该这样做 - 我认为这是我在前面几点中的第一个问题的原因。
此外,答案 link(一个片段中的一次搜索)中使用的模式可能不适合您的模式(一次搜索多个片段)。对于所有嵌套的 Fragment.

,您应该在 parent Activity 中调用一个 SearchView

解决方法:我是这样解决的:

#1 使用 parent SearchView:

它将避免重复的功能,并让parent activity监督其所有child人。此外,这将避免菜单中出现重复图标。
这是主要的 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 小部件:

就像我之前说的,我无法让它与 filter2() 一起工作,所以我创建了一个 Filter class 作为网络上的任何示例。
它很快看起来像,在嵌套片段的适配器中,如下:

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

activity - 带有搜索视图 - 是 Observable object,嵌套片段是 Observers (see Observer pattern)。基本上,当 onQueryTextChange 被调用时,它会触发现有观察者中的 update() 方法。
这是 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 class 将监听和 "pass" 更新的数据:

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.
中初始化它们时这样做 所以在 parent 片段中,我通过传递 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 到 parent observable 并在 child 片段被销毁时将其删除。
这是 parent 片段的 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() 调用 activity 中的 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 general object 并将其添加为观察者。实际上,我必须使用特定片段 class 进行转换和初始化(此处为 NestedFragment);可能有一个简单的解决方案,但我暂时没有找到。

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


#5 改进(编辑):

  • (参见 *) 您可以通过在所有嵌套片段上保持全局 Fragment class 扩展来添加观察者。这就是我如何将片段实例化为 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 class 如下:

    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) { }
    }