Android ListView non si aggiorna dopo notifyDataSetChanged


116

Il mio codice ListFragment

public class ItemFragment extends ListFragment {

    private DatabaseHandler dbHelper;
    private static final String TITLE = "Items";
    private static final String LOG_TAG = "debugger";
    private ItemAdapter adapter;
    private List<Item> items;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.item_fragment_list, container, false);        
        return view;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.setHasOptionsMenu(true);
        super.onCreate(savedInstanceState);
        getActivity().setTitle(TITLE);
        dbHelper = new DatabaseHandler(getActivity());
        items = dbHelper.getItems(); 
        adapter = new ItemAdapter(getActivity().getApplicationContext(), items);
        this.setListAdapter(adapter);

    }



    @Override
    public void onResume() {
        super.onResume();
        items.clear();
        items = dbHelper.getItems(); //reload the items from database
        adapter.notifyDataSetChanged();
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        if(dbHelper != null) { //item is edited
            Item item = (Item) this.getListAdapter().getItem(position);
            Intent intent = new Intent(getActivity(), AddItemActivity.class);
            intent.putExtra(IntentConstants.ITEM, item);
            startActivity(intent);
        }
    }
}

Il mio ListView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Ma questo non aggiorna il file ListView. Anche dopo il riavvio dell'app, gli elementi aggiornati non vengono visualizzati. Il mio ItemAdaptersi estendeBaseAdapter

public class ItemAdapter extends BaseAdapter{

    private LayoutInflater inflater;
    private List<Item> items;
    private Context context;

    public ProjectListItemAdapter(Context context, List<Item> items) {
        super();
        inflater = LayoutInflater.from(context);
        this.context = context;
        this.items = items;

    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public Object getItem(int position) {
        return items.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemViewHolder holder = null;
        if(convertView == null) {
            holder = new ItemViewHolder();
            convertView = inflater.inflate(R.layout.list_item, parent,false);
            holder.itemName = (TextView) convertView.findViewById(R.id.topText);
            holder.itemLocation = (TextView) convertView.findViewById(R.id.bottomText);
            convertView.setTag(holder);
        } else {
            holder = (ItemViewHolder) convertView.getTag();
        }
        holder.itemName.setText("Name: " + items.get(position).getName());
        holder.itemLocation.setText("Location: " + items.get(position).getLocation());
        if(position % 2 == 0) {                                                                                 
            convertView.setBackgroundColor(context.getResources().getColor(R.color.evenRowColor));
        } else {    
            convertView.setBackgroundColor(context.getResources().getColor(R.color.oddRowColor));
        }
        return convertView;
    }

    private static class ItemViewHolder {
        TextView itemName;
        TextView itemLocation;
    }
}

Qualcuno può aiutare per favore?


2
Hai testato per vedere se l'operazione del database funziona correttamente? Come appare l'adattatore? Inoltre, se crei un oggetto on per il adapterriferimento, perché lo provi per null una riga sotto?
Luksprog

Il codice non genera eccezioni e ho controllato utilizzando il debug. Tutti i metodi eseguiti senza errori. Sì, è uno stupido errore.
Coder

Risposte:


229

Guarda il tuo onResumemetodo in ItemFragment:

@Override
public void onResume() {
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); // reload the items from database
    adapter.notifyDataSetChanged();
}

quello che hai appena aggiornato prima di chiamare notifyDataSetChanged()non è il campo dell'adattatore private List<Item> items;ma il campo dichiarato in modo identico del frammento. L'adattatore memorizza ancora un riferimento all'elenco di elementi passati quando è stato creato l'adattatore (ad esempio in onCreate di fragment). Il modo più breve (nel senso del numero di modifiche) ma non elegante per far comportare il codice come previsto è semplicemente sostituire la riga:

    items = dbHelper.getItems(); // reload the items from database

con

    items.addAll(dbHelper.getItems()); // reload the items from database

Una soluzione più elegante:

1) rimuovi elementi private List<Item> items;da ItemFragment: è necessario mantenerli solo nell'adattatore

2) cambia su Crea in:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.setHasOptionsMenu(true);
    getActivity().setTitle(TITLE);
    dbHelper = new DatabaseHandler(getActivity());
    adapter = new ItemAdapter(getActivity(), dbHelper.getItems());
    setListAdapter(adapter);
}

3) aggiungi metodo in ItemAdapter:

public void swapItems(List<Item> items) {
    this.items = items;
    notifyDataSetChanged();
}

4) cambia il tuo onResume in:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}

Non sarebbe più pulito spostare l'intera cosa dbHelper nell'adattatore? Quindi chiameresti solo adapter.swapItems();e l'adattatore farebbe la dbHelper.getItems()roba. Ma comunque grazie per la risposta :)
Ansgar

7
Perché dovresti deselezionare () e aggiungere di nuovo gli elementi? Non è esattamente questo lo scopo di notifyDataSetChanged()?
Phil Ryan

1
@tomsaz mi potete aiutare con questo stackoverflow.com/questions/28148618/...

1
Grazie @tomsaz Gawel, i tuoi swapItems mi aiutano davvero molto, non so perché il mio adattatore.notifydatasetchanged non funziona, poiché anche la "lista" che sto superando è aggiornata, anche io l'ho controllata stampando il registro, puoi spiegarmi questo concept
Kimmi Dhingra

1
Questa risposta è corretta. Il problema è che la Item ArrayList dell'ADAPTER non veniva aggiornata. Ciò significa che puoi chiamare notifydatasetchanged finché il tuo viso non è blu senza alcun effetto. L'adattatore sta aggiornando il set di dati con lo stesso set di dati, quindi NON ci sono modifiche. Un'altra alternativa alla soluzione pubblicata in questa risposta che potrebbe essere più pulita è: adapter.items = items; adapter.notifyDataSetChanged ();
Ray Li

23

Stai assegnando elementi ricaricati a elementi variabili globali in onResume(), ma ciò non si rifletterà inItemAdapter classe, perché ha una propria variabile di istanza chiamata "elementi".

Per l'aggiornamento ListView, aggiungi un refresh () nella ItemAdapterclasse che accetta i dati della lista, cioè gli elementi

class ItemAdapter
{
    .....

    public void refresh(List<Item> items)
    {
        this.items = items;
        notifyDataSetChanged();
    } 
}

aggiornare onResume()con il seguente codice

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}

1
Questo è esattamente giusto. Il costruttore dell'adattatore si aspetta che vengano passati gli elementi, ma aggiorna sempre e solo il campo della classe esterna.
LuxuryMode

Ciao Santhosh. Si può dare un'occhiata a un problema simile: stackoverflow.com/questions/35850715/...

8

In onResume () cambia questa riga

items = dbHelper.getItems(); //reload the items from database

per

items.addAll(dbHelper.getItems()); //reload the items from database

Il problema è che non comunichi mai al tuo adattatore l'elenco dei nuovi elementi. Se non vuoi passare un nuovo elenco al tuo adattatore (come sembra che tu non lo faccia), usa semplicemente items.addAlldopo il tuo clear(). Ciò assicurerà di modificare lo stesso elenco a cui l'adattatore ha un riferimento.


E 'confusione che adapter.clear()non forza l'adattatore per rendersi conto che la vista deve aggiornare, ma adapter.add()o adapter.addAll()lo fa. Grazie per la risposta però!
w3bshark

Nota che stavo usando items.addAll()e non adapter.addAll (). L'unica cosa che consente all'adattatore di reagire alle modifiche è il file notifyDataSetChanged. Il motivo per cui l'adattatore vede le modifiche è che l' itemselenco è lo stesso elenco utilizzato dall'adattatore.
Justin Breitfeller

4

Se l'adattatore è già impostato, impostarlo di nuovo non aggiornerà la visualizzazione elenco. Invece prima controlla se listview ha un adattatore e poi chiama il metodo appropriato.

Penso che non sia una buona idea creare una nuova istanza dell'adattatore durante l'impostazione della visualizzazione elenco. Invece, crea un oggetto.

BuildingAdapter adapter = new BuildingAdapter(context);

    if(getListView().getAdapter() == null){ //Adapter not set yet.
     setListAdapter(adapter);
    }
    else{ //Already has an adapter
    adapter.notifyDataSetChanged();
    }

Inoltre potresti provare a eseguire l'elenco di aggiornamento sul thread dell'interfaccia utente:

activity.runOnUiThread(new Runnable() {         
        public void run() {
              //do your modifications here

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});

Non sono sicuro di come implementare il thread dell'interfaccia utente. La mia attività principale ha 3 frammenti (schede) e il codice nella domanda è relativo a uno dei frammenti che contiene la visualizzazione elenco. Il motivo per il passaggio di elementi a ItemAdapterè che desidero colorare le righe e la visualizzazione elenco mostra più elementi di dati. Ho pubblicato il codice per l'adattatore.
Coder

È necessario inserire il codice che popola l'elenco nel mio codice di esempio utilizzando "this". invece di "attività"
AlexGo

In alcuni casi non viene aggiornato quando si esegue notifyDataSetChanged () in thread diversi, quindi la soluzione sopra è giusta per alcuni casi.
Ayman Al-Absi

4

Se vuoi aggiornare il tuo listview non importa se vuoi farlo su onResume(), onCreate()o in qualche altra funzione, la prima cosa che devi capire è che non avrai bisogno di creare una nuova istanza dell'adattatore, basta compilare di nuovo gli array con i tuoi dati. L'idea è qualcosa di simile a questa:

private ArrayList<String> titles;
private MyListAdapter adapter;
private ListView myListView;

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    myListView = (ListView) findViewById(R.id.my_list);

    titles = new ArrayList<String>()

    for(int i =0; i<20;i++){
        titles.add("Title "+i);
    }

    adapter = new MyListAdapter(this, titles);
    myListView.setAdapter(adapter);
}


@Override
public void onResume(){
    super.onResume();
    // first clear the items and populate the new items
    titles.clear();
    for(int i =0; i<20;i++){
        titles.add("New Title "+i);
    }
    adapter.notifySetDataChanged();
}

Quindi, a seconda di quella risposta, dovresti usare lo stesso List<Item>nel tuo file Fragment. Nella prima inizializzazione dell'adattatore si riempie l'elenco con gli elementi e si imposta l'adattatore sul listview. Dopodiché in ogni modifica dei tuoi articoli devi cancellare i valori dal principale List<Item> itemse poi popolarlo di nuovo con i tuoi nuovi articoli e chiamare notifySetDataChanged();.

È così che funziona :).


Grazie per la risposta. Ho apportato le modifiche come hai detto. Ho pubblicato il mio codice. Ancora non funziona. Ora non mostra nemmeno la visualizzazione elenco quando vengono aggiunti nuovi elementi.
Coder

Ho cambiato il codice. La cosa strana da osservare è che l'elemento non viene aggiornato in DB
Coder

Questo thread è per il database stackoverflow.com/questions/14555332/…
Coder

3

Una risposta di AlexGo ha fatto il trucco per me:

getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {
         messages.add(m);
         adapter.notifyDataSetChanged();
         getListView().setSelection(messages.size()-1);
        }
});

List Update ha funzionato per me prima quando l'aggiornamento è stato attivato da un evento della GUI, quindi nel thread dell'interfaccia utente.

Tuttavia, quando aggiorno l'elenco da un altro evento / thread, ovvero una chiamata dall'esterno dell'app, l'aggiornamento non si trova nel thread dell'interfaccia utente e ignora la chiamata a getListView. Chiamare l'aggiornamento con runOnUiThread come sopra ha fatto il trucco per me. Grazie!!


3

Prova questo

@Override
public void onResume() {
super.onResume();
items.clear();
items = dbHelper.getItems(); //reload the items from database
adapter = new ItemAdapter(getActivity(), items);//reload the items from database
adapter.notifyDataSetChanged();
}

3
adpter.notifyDataSetInvalidated();

Prova questo nel onPause()metodo della classe di attività.


1
adapter.setNotifyDataChanged()

dovrebbe fare il trucco.


3
dove mettere è la domanda qui ??
swiftBoy

1

Se l'elenco è contenuto nell'adattatore stesso, dovrebbe chiamare anche la chiamata alla funzione che aggiorna l'elenco notifyDataSetChanged().

L'esecuzione di questa funzione dal thread dell'interfaccia utente ha fatto il trucco per me:

La refresh()funzione all'interno dell'adattatore

public void refresh(){
    //manipulate list
    notifyDataSetChanged();
}

Quindi, a sua volta, esegui questa funzione dal thread dell'interfaccia utente

getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});

Questo in effetti ha fatto la differenza per me poiché l'aggiornamento è arrivato sulla rete tramite un thread diverso.
Chuck

0

Prova in questo modo:

this.notifyDataSetChanged();

invece di:

adapter.notifyDataSetChanged();

Devi notifyDataSetChanged()il ListViewnon alla classe dell'adattatore.


Certo che no, unica possibilità se l'attività viene estesa da una listview
cmario
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.