Android Fragments SearchView未在TabLayout的每个子选项卡中进行筛选

5n0oy7gb  于 2022-11-24  发布在  Android
关注(0)|答案(3)|浏览(95)

在这里,我在一个Activity中有一个toolbar,它包含一个SearchView。这个Activity有多个片段。其中一个主片段有10个以上的片段。所有10个片段都在列表视图中显示数据。现在我尝试通过MainActivitySearchView来过滤所有片段列表。但是它从来没有过滤每个片段的列表。现在,我将向您展示我是如何实现这一切的。
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;
}

Adapter类内的Filter方法。

// 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();
}
ryhaxcpt

ryhaxcpt1#

你可以通过使用Observable/Observer模式来管理嵌套列表上的过滤器,这将从一个Observable parent更新每个嵌套列表。我修复了all troubles,它现在工作得很好,可以实现正确的行为。
因此,以下是我为实现这一目标所做的工作:
1.在Activity中使用一个父SearchView
1.(可选)在嵌套列表Adapter中创建Filter类(* android. widget. Filter *)
1.然后,使用Observable/Observer模式来嵌套FragmentActivity

    • 背景:**当我尝试您的代码时,遇到了三个问题:
  • 我无法使用ActionBar进行搜索:onQueryTextChange似乎从未在Fragment s中调用过。当我点击搜索图标时,我觉得SearchView(编辑文本、图标等)似乎没有与搜索小部件连接(而是与Activity的小部件连接)。
  • 我无法运行自定义方法filter2:我的意思是,当我解决了前面的问题时,这个方法不起作用。实际上,我不得不使用Filter扩展的自定义类及其两个方法:performFilteringpublishResults。如果没有它,当我点击搜索栏中的一个单词时,我会得到一个空白屏幕。然而,这可能只是我的代码,也许filter2()非常适合你...
  • 我不能在片段之间进行持久搜索:对于每个子片段,都会创建一个新的SearchView。在我看来,您在嵌套片段中重复调用了这一行SearchView sv = new SearchView(...);。因此,每次我切换到下一个片段时,扩展的搜索视图都会删除它之前的文本值。

无论如何,经过一些研究,我在SO上找到了关于实现Search fragmentthis answer。几乎和你的代码一样,除了你在父活动和片段中"复制"了选项菜单代码。你不应该这样做-我认为这是我前面第一个问题的原因。
此外,答案链接中使用的模式(一个片段中的一个搜索)可能不适用于您的模式(多个片段中的一个搜索),您应该在父Activity中调用一个SearchView来表示所有嵌套的Fragment

    • 解决方案:**这是我的管理方式:
  • #1使用父项SearchView*

这将避免重复的功能,并让父Activity监督它的所有子Activity。此外,这将避免菜单中出现重复图标。
这是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类,作为Web上的任何示例。
它快速地看起来像,在适配器的嵌套片段中,如下所示:

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(带有searchview)是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;
    }
}

为了添加observer片段来侦听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结论:*

这样做效果很好,所有嵌套片段中的列表都可以通过一个searchview按预期进行更新。但是,在我上面的代码中有一个不方便的地方,你应该注意:

    • (参见下面的改进)* 我不能调用Fragment通用对象并将其添加为观察者。实际上,我必须使用特定的片段类(这里是NestedFragment)进行转换和初始化;也许有一个简单解决办法,但我暂时没有找到。

尽管如此,我还是得到了正确的行为,而且--我认为--让一个搜索小工具保持在顶部的活动状态可能是一个很好的模式。因此,通过这个解决方案,你可以得到一个线索,一个正确的方向,来实现你想要的。我希望你会喜欢。

  • #5改进(编辑):*
    • *(参见 *)**您可以通过在所有嵌套片段上保留一个全局Fragment类扩展来添加观察器。
@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) { }
}
nzk0hqpo

nzk0hqpo2#

为了更好地理解问题。1.操作栏中有一个搜索视图2.有多个片段(来自图片Advisory/TopAdvisors...)3.在每个片段中,每个选项卡都有多个片段(例如Equity...等)4.您希望片段中的所有列表视图根据搜索内容筛选其数据
对不对?
在您当前的实施中,状态是什么?当前显示的列表视图是否已过滤?
然后你需要检查notifydatasetChanged是否正确地传播到适配器。这意味着它应该在UIThread本身上。
对于所有片段的更新,即当您键入搜索文本时不在屏幕上的片段,您需要考虑片段的生命周期,并在片段的onResume中包含代码,以确保列表在用于初始化适配器之前已被过滤。这适用于您已键入搜索文本,现在正在选项卡/片段之间移动的情况。因此,片段会在屏幕上显示和显示,因此需要onResume。
更新:包括检查搜索框的文本,如果它有东西调用adapter.filter2()在onResume,因为这基本上是什么过滤您的列表。

zvms9eto

zvms9eto3#

  • 问题是-当您启动一个片段时,它"接管" Activity的searchView,并且在启动此片段之后您键入的任何内容都会调用JUSTTHIS片段的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中有不同的搜索视图示例
  • 您需要在每个示例之间同步搜索查询,使每个示例都知道其他示例拥有什么。

我不推荐这种设计,我宁愿做的是

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

相关问题