web-dev-qa-db-ja.com

解決済み:SearchViewはTabLayoutの各子タブでフィルタリングしません

ここでは、toolbarを含むActivitySearchViewがあります。そして、そのアクティビティには複数のフラグメントがあります。それらのうちの1つの主要なフラグメントには、さらに10個のフラグメントが含まれています。 10個のフラグメントすべてがリストビューにデータを表示しています。今、私はフラグメントのすべてのリストをSearchViewMainActivityでフィルタリングしようとしています。ただし、各フラグメントのリストをフィルタリングすることはありません。次に、すべてをどのように実装したかを示します。

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 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();
}
22
Anshul Tyagi

ネストされたリストのフィルターはObservable/Observerパターンを使用で管理できます。これにより、ネストされた各リストが1つから更新されます Observable parentすべてのトラブル を修正しましたが、正しい動作を実現するために正常に機能するようになりました。

したがって、これを達成するために私がしたことは次のとおりです。

  1. SearchViewで1つの親Activityを使用する
  2. (オプション)ネストされたリストFilterAdapterクラス(Android.widget.Filter)を作成します
  3. 次に、ネストされたObservableObserverFragment/Activityパターンを使用します

背景:コードを試したところ、次の3つの問題が発生しました。

  • ActionBarを使用して検索を実行できません:onQueryTextChangeFragmentsで呼び出されることはないようです。検索アイコンをタップすると、SearchView(編集テキスト、アイコンなど)が検索ウィジェットに添付されていないように見えます(アクティビティのウィジェットに添付されています)。
  • カスタムメソッドを実行できません_filter2_:つまり、前のポイントを解決したときに、このメソッドが機能しません。実際、私はFilterとその2つのメソッドperformFilteringpublishResultsによって拡張されるカスタムクラスを試してみる必要があります。それがないと、検索バーで単語をタップすると空白の画面が表示されます。ただし、これは私のコードにすぎない可能性があり、おそらくfilter2()は完全に機能します...
  • フラグメント間で永続的な検索を行うことはできません。子フラグメントごとに、新しいSearchViewが作成されます。ネストされたフラグメントでこの行を繰り返しSearchView sv = new SearchView(...);と呼んでいるようです。したがって、次のフラグメントに切り替えるたびに、展開された検索ビューは前のテキスト値を削除します。

とにかく、いくつかの調査の結果、 この回答 on SO 検索フラグメント の実装について)が見つかりました。ただし、それ以外はほぼ同じコードです。親アクティビティとフラグメントでオプションメニューコードを「複製」します。そうすべきではありません。これが、前のポイントでの最初の問題の原因だと思います。
その上、回答のリンクで使用されているパターン(1つのフラグメントで1回の検索)があなたのパターン(複数のフラグメントでの1回の検索)に適合していない可能性があります。ネストされたすべてのSearchViewに対して、親Activityで1つの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()で動作させることができないので、Web上の例として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オブジェクトであり、ネストされたフラグメントはObserversです( オブザーバーパターンを参照 )。基本的に、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()で呼び出されると、これはObserverを実装しているupdate()メソッドのネストされたフラグメントで受信されます。
これは、フィルタリングする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結論:

これはうまく機能し、ネストされたすべてのフラグメントのリストは、1つの検索ビューで期待どおりに更新されます。ただし、上記のコードには、次の点に注意する必要がある不便があります。

  • (以下の改善点を参照) Fragment汎用オブジェクトを呼び出して、それをオブザーバーとして追加することはできません。実際、特定のフラグメントクラス(ここではNestedFragment)をキャストして初期化する必要があります。簡単な解決策があるかもしれませんが、私は今のところそれを見つけられませんでした。

それにもかかわらず、私は正しい振る舞いをします、そして-私は思う-それは活動の中で1つの検索ウィジェットを一番上に保つことによって良いパターンかもしれません。したがって、このソリューションを使用すると、目的を達成するための手がかり、正しい方向性を得ることができます。楽しんでいただければ幸いです。


#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) { }
    }    
    _
13
fllo

質問をよりよく理解するためだけに。 1.アクションバーに検索ビューがあります2.複数のフラグメントがあります(画像Advisory/TopAdvisorsから...)3。これらのそれぞれの中に、タブごとに複数のフラグメントがあります(例:エクイティ...など)4 。フラグメント内のすべてのリストビューで、検索コンテンツに基づいてデータをフィルタリングする必要があります

正しい??

現在の実装では、ステータスは何ですか?現在表示されているリストビューはフィルタリングされていますか?

それともそれでも機能していませんか?次に、notifydatasetChangedがアダプタに正しく伝播していることを確認する必要があります。つまり、UIThread自体にある必要があります。

すべてのフラグメントを更新するには、つまり、検索テキストを入力したときに画面に表示されなかったものを更新するには、フラグメントのライフサイクルを考慮し、フラグメントのonResumeにコードを含めて、アダプターの初期化に使用する前にリストがフィルター処理されていることを確認する必要があります。 。これは、検索テキストをすでに入力していて、タブ/フラグメント間を移動しているシナリオ用です。したがって、フラグメントは画面のオンとオフを切り替えるため、onResumeが必要になります。

更新:テキストの検索ボックスをチェックし、onResumeにadapter.filter2()を呼び出すものがある場合は、それが基本的にリストをフィルタリングしているためです。 。

1
prasunnair
  • 問題は、フラグメントを起動すると、アクティビティのsearchViewが「引き継がれ」、このフラグメントが起動された後に入力すると、このフラグメントのonQueryTextChangeリスナーが呼び出されることです。したがって、他のフラグメントでは検索が行われません。
  • フラグメントが起動されたときに最後の検索クエリ(他のフラグメントによって投稿されたもの)をチェックし、そのクエリをそれ自体で検索するようにします。また、検索クエリの変更は、アクティビティの検索ビューにも投稿して、他のフラグメントが起動時に読み取れるようにする必要があります。

ビューページャー/タブは、表示されているフラグメントを切り替えるたびに破棄されるように実装されていると思います。

次の変更を加えます(コメントを読んでください)

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

私はあなたが一般的な考えを得ることを望みます

  • フラグメントとアクティビティごとに異なる検索ビューインスタンスがあります
  • それぞれの間で検索クエリを同期し、他のインスタンスが何を持っているかをそれぞれに認識させる必要があります。

私はこのデザインをお勧めしませんが、私がしたいのは

  • アクティビティの検索ビューと、アクティビティの検索ビューのonQueryTextChangeリスナーを使用するだけです。
  • アクティビティ内のonQueryTextChangeから存続しているすべてのフラグメントに通知します(フラグメントは、onQueryTextChangeリスナーをそれぞれのフラグメントのonResumeおよびonPause内のアクティビティに登録および登録解除します)。 [補足-このデザインパターンは オブザーバー パターンと呼ばれます]
1
Vinay Wadhwa