Подтвердить что ты не робот

Решено: SearchView не фильтрует в каждой дочерней вкладке TabLayout

Здесь у меня есть toolbar в Activity, который содержит SearchView. И эта деятельность имеет несколько фрагментов. Один из основных фрагментов из них имеет еще 10 фрагментов внутри себя. Все 10 фрагментов отображают данные в списках. Теперь я пытаюсь отфильтровать все списки фрагментов на SearchView из 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;
}

Метод фильтра внутри класса адаптера.

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

Ответ 1

Вы можете управлять фильтром по вложенному списку с помощью с использованием шаблона Observable/Observer, это обновит каждый вложенный список из одного Наблюдаемый родитель. Я исправил все проблемы, и теперь он хорошо работает для достижения правильного поведения.

Итак, вот что я сделал для этого:

  • Использование одного родителя SearchView в Activity
  • (необязательно) Создайте Filter class (android.widget.Filter) во вложенном списке Adapter
  • Затем, используя шаблон Observable/Observer для вложенных Fragment с Activity

Фон: Когда я пробовал свой код, у меня было три проблемы:

  • Я не могу выполнить поиск с помощью ActionBar: onQueryTextChange, похоже, никогда не вызывается в Fragment s. Когда я нажимаю на значок поиска, мне кажется, что SearchView (edittext, значок и т.д.) Не привязан к виджету поиска (но прикреплен к виджету активности).
  • Я не могу запустить пользовательский метод filter2: я имею в виду, когда я решил предыдущую точку, этот метод не работает. В самом деле, я должен играть с пользовательским классом, расширяющимся на Filter и двумя его методами: performFiltering и publishResults. Без этого я получил пустой экран, когда я нажимаю слово в строке поиска. Однако это может быть только мой код и, возможно, filter2() отлично работает для вас...
  • Я не могу иметь постоянный поиск между фрагментами: для каждого дочернего фрагмента создается новый SearchView. Мне кажется, что вы повторяете эту строку SearchView sv = new SearchView(...); во вложенном фрагменте. Поэтому каждый раз, когда я переключаюсь на следующий фрагмент, расширенное searchview удаляет его предыдущее текстовое значение.

В любом случае, после некоторых исследований, я нашел этот ответ на SO о реализации фрагмента поиска. Почти тот же код, что и ваш, за исключением того, что вы "дублируете" код меню параметров в родительской активности и фрагментах. Вы не должны этого делать - я думаю, что это причина моей первой проблемы в предыдущих пунктах.
Кроме того, шаблон, используемый в ответной ссылке (один поиск в одном фрагменте), не может быть адаптирован к вашему (один поиск нескольких фрагментов). Вы должны вызвать один SearchView в родительском Activity для всех вложенных Fragment.


Решение: Вот как мне это удалось:

# 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:

Активность - с помощью searchview - это объект Observable, а вложенные фрагменты - это Observer (см. шаблон наблюдателя), В принципе, когда будет вызван 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
    );
}

Адаптер добавляет наблюдателя родительскому наблюдаемому и удаляет его, когда дочерние фрагменты уничтожаются.
Здесь 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);
    }
}

Наконец, когда filterManager.setQuery() в деятельности вызывается с onQueryTextChange(), это будет получено во вложенном фрагменте в update(), который реализует Observer.
Это вложенные фрагменты с 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 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) { }
    }    
    

Ответ 2

Просто чтобы лучше понять вопрос. 1. У вас есть поиск в панели действий 2. У вас есть несколько фрагментов (из вашего изображения Консультант /TopAdvisors...) 3. Внутри каждого из них есть несколько фрагментов для каждой вкладки (пример Equity... и т.д.), 4. Вы хотите, чтобы все виды списков в фрагментах отфильтровывали свои данные на основе содержимого поиска

Right??

В вашей текущей реализации каков статус? Отображается ли текущее отображение списка, которое фильтруется?

Или даже это не работает? Затем вам нужно проверить notifydatasetChanged правильно распространяется на адаптер. Это значит, что это должно быть на самом UIThread.

Для обновлений для всех фрагментов, означающих один раз, который не был на экране при вводе текста поиска, вам необходимо рассмотреть жизненный цикл фрагмента и включить код в onResume фрагмента, чтобы убедиться, что список фильтруется, прежде чем он будет использоваться для инициализируйте адаптер. Это для сценария, в котором вы уже ввели текст поиска и теперь перемещаетесь между вкладками/фрагментами. Таким образом, фрагменты будут отображаться и выходить за пределы экрана, поэтому необходимо использовать onResume.

Обновление: Включите проверку окна поиска для текста, и если у него есть что-то, вызывающее adapter.filter2() в onResume, потому что это в основном то, что фильтрует ваш список..

Ответ 3

  • проблема заключается в том, что при запуске фрагмента он "захватывает" объект searchView и все, что вы набираете ПОСЛЕ того, как этот фрагмент запускается, вызывает прослушиватель onQueryTextChange фрагмента JUST THIS. Следовательно, поиск не происходит ни в одном другом фрагмент..
  • Вы хотите, чтобы ваши фрагменты проверяли последний поисковый запрос (отправленный любым другим фрагментом) при их запуске и выполняли поиск в нем для этого запроса. Кроме того, любое изменение в поисковом запросе должно быть также опубликовано в представлении поиска активности, чтобы другие фрагменты могли прочитать его при запуске.

Я предполагаю, что ваш 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 search view query in the fragment 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 searchview query with fragment searchview query, so other fragments can read from the activity 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 searchview query with fragment searchview query
                return false;
            }
        });

Надеюсь, вы получите общую идею

  • У вас есть разные экземпляры вида поиска в каждом из ваших фрагментов и активности
  • вам нужно синхронизировать поисковые запросы между каждым из них, чтобы каждый из них знал о том, что есть у других экземпляров.

Я не рекомендую этот дизайн, хотя я бы предпочел сделать это

  • просто используйте представление поиска активности и в onQueryTextChange слушателя вида поиска активности.
  • я бы уведомил все фрагменты, которые являются живыми из onQueryTextChange в активности (фрагменты будут регистрироваться и отменять регистрацию onQueryTextChange слушателей с активностью в onResume и onPause соответствующих фрагментов). [Сторона примечания. Этот шаблон дизайна называется Observer pattern]