Questa è la disposizione più valida che troverai, ma guardala attentamente e vedi se è compatibile con il tuo lavoro.
Soluzione:
È possibile gestire il filtro sull'elenco annidato tramite utilizzando uno schema Osservabile/Osservatore Questo aggiornerà ogni elenco annidato da un Observable genitore. Ho risolto tutti i problemi e ora funziona bene per ottenere il giusto comportamento.
Ecco quindi cosa ho fatto per ottenere questo risultato:
- Usare un genitore
SearchView
inActivity
- (opzionale) Creare un
Filter
(android.widget.Filter) nell'elenco annidatoAdapter
- Quindi, utilizzando un elemento
Observable
/Observer
per gli elenchi nidificatiFragment
conActivity
Sfondo: Quando ho provato il tuo codice, ho avuto tre problemi:
- Non posso effettuare una ricerca utilizzando la barra delle azioni:
onQueryTextChange
sembra non essere mai chiamato inFragment
s. Quando tocco l'icona di ricerca, mi sembra cheSearchView
(edittext, icona, ecc.) non sia collegato al widget di ricerca (ma al widget dell'attività). - Non riesco a eseguire il metodo personalizzato
filter2
: Cioè, quando ho risolto il punto precedente, questo metodo non funziona. In effetti, devo giocare con la classe personalizzata che si estende perFilter
e i suoi due metodi:performFiltering
epublishResults
. Senza di esso, ottengo una schermata vuota quando tocco una parola nella barra di ricerca. Tuttavia, questo potrebbe essere solo il mio codice e forsefilter2()
funziona perfettamente per voi... - Non posso avere una ricerca persistente tra i frammenti: per ogni frammento figlio un nuovo elemento
SearchView
viene creato. Mi sembra che si chiami ripetutamente questa lineaSearchView sv = new SearchView(...);
nei frammenti annidati. Quindi, ogni volta che passo al frammento successivo, la searchview espansa rimuove il valore del testo precedente.
Comunque, dopo alcune ricerche, ho trovato questa risposta su SO sull'implementazione di un frammento di ricerca. Quasi lo stesso codice del tuo, tranne per il fatto che "duplichi" il codice del menu delle opzioni nell'attività principale e nei frammenti. Non dovresti farlo - penso che sia la causa del mio primo problema nei punti precedenti.
Inoltre, lo schema utilizzato nel link della risposta (una ricerca in un frammento) potrebbe non essere adatto al tuo (una ricerca per più frammenti). Dovresti chiamarne uno SearchView
nel genitore Activity
per tutti i frammenti annidati Fragment
.
Soluzione: Ecco come l'ho gestito:
#1 Usare un genitore SearchView
:
Questo eviterà la duplicazione delle funzioni e permetterà all'attività genitore di supervisionare tutti i suoi figli. Inoltre, questo eviterà la duplicazione dell'icona nel menu.
È il genitore principale Activity
classe:
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 (opzionale) Creare una classe Filter
widget:
Come ho detto in precedenza, non riesco a farlo funzionare con filter2()
quindi creo un widget Filter
come qualsiasi esempio presente sul web.
Si presenta rapidamente, nell'adattatore del frammento annidato, come segue:
private ArrayList originalList; // I used String objects in my tests
private ArrayList 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 list = originalList;
int count = list.size();
final ArrayList 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) results.values;
notifyDataSetChanged();
}
}
#3 Utilizzando un Observable
/Observer
modello:
L'attività - con la vista di ricerca - è l'elemento Observable
e i frammenti annidati sono gli oggetti Observer
(vedere lo schema Observer). In pratica, quando l'oggetto onQueryTextChange
sarà chiamato, attiverà il metodo update()
negli osservatori esistenti.
Ecco la dichiarazione nel genitore 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;
}
Questo è il metodo Observable
che ascolterà e "passerà" i dati aggiornati:
public class FilterManager extends Observable {
private String query;
public void setQuery(String query) {
this.query = query;
setChanged();
notifyObservers();
}
public String getQuery() {
return query;
}
}
Per aggiungere i frammenti dell'osservatore per ascoltare il valore della searchview, lo faccio quando sono inizializzati nella classe FragmentStatePagerAdapter
.
Quindi, nel frammento genitore, creo le schede di contenuto passando la classe 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
);
}
L'adattatore aggiungerà l'osservatore all'osservabile genitore e lo rimuoverà quando i frammenti figli saranno distrutti.
Ecco il file ViewPagerAdapter
del frammento genitore:
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);
}
}
Infine, quando filterManager.setQuery()
nell'attività viene chiamato con onQueryTextChange()
questo verrà ricevuto in un frammento annidato in update()
che implementano il metodo Observer
.
Questo è il frammento annidato con il metodo ListView
da filtrare:
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 Conclusione:
Questo funziona bene, gli elenchi in tutti i frammenti annidati sono aggiornati come previsto da una sola searchview. Tuttavia, c'è un'incoveniente nel mio codice precedente di cui si dovrebbe essere consapevoli:
- (vedere i miglioramenti sotto)
Non posso chiamareFragment
oggetto generale e aggiungerlo come osservatore. Infatti, devo eseguire il cast e l'init con la classe specifica del frammento (quiNestedFragment
); potrebbe esserci una soluzione semplice, ma per ora non l'ho trovata.
Ciononostante, ottengo il comportamento corretto e - penso - potrebbe essere un buon modello mantenere un widget di ricerca in cima, in attività. Con questa soluzione, quindi, si può ottenere un indizio, una direzione giusta, per ottenere ciò che si desidera. Spero che vi piaccia.
#5 Miglioramenti (modifica):
-
(vedi *) Si possono aggiungere gli osservatori mantenendo un'opzione globale
Fragment
globale su tutti i frammenti annidati. È così che istanzio i miei frammenti nella classeViewPager
:@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); }
Creando la classe
ObserverFragment
come segue:public class ObserverFragment extends Fragment implements Observer { public void update(Observable obs, Object obj) { /* do nothing here */ } }
E poi, estendendo e sovrascrivendo la classe
update()
nei frammenti annidati:public class FirstNestedFragment extends ObserverFragment { @Override public void update(Observable obs, Object obj) { } }