Skip to content

Risolto: SearchView non filtra in ogni scheda figlio di TabLayout

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:

  1. Usare un genitore SearchView in Activity
  2. (opzionale) Creare un Filter (android.widget.Filter) nell'elenco annidato Adapter
  3. Quindi, utilizzando un elemento Observable/Observer per gli elenchi nidificati Fragment con Activity

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 in Fragments. Quando tocco l'icona di ricerca, mi sembra che SearchView (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 per Filter e i suoi due metodi: performFiltering e publishResults. Senza di esso, ottengo una schermata vuota quando tocco una parola nella barra di ricerca. Tuttavia, questo potrebbe essere solo il mio codice e forse filter2() 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 linea SearchView 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 chiamare Fragment oggetto generale e aggiungerlo come osservatore. Infatti, devo eseguire il cast e l'init con la classe specifica del frammento (qui NestedFragment); 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 classe 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);
    }
    

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

Ecco le recensioni e le valutazioni



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.