Android ListView nie odświeża się po notifyDataSetChanged


116

Mój kod 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);
        }
    }
}

Mój 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>

Ale to nie odświeża ListView. Nawet po ponownym uruchomieniu aplikacji zaktualizowane elementy nie są wyświetlane. Moje ItemAdapterprzedłużeniaBaseAdapter

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

Czy ktoś może pomóc?


2
Czy przetestowałeś, czy operacja bazy danych działa poprawnie? Jak wygląda adapter? Ponadto, jeśli utworzysz obiekt on dla adapterodniesienia, dlaczego testujesz go pod kątem zerowej jednej linii poniżej?
Luksprog

Kod nie zgłasza wyjątku i sprawdziłem za pomocą debugowania. Wszystkie metody zostały wykonane bez błędów. Tak, to głupi błąd.
Coder

Odpowiedzi:


229

Spójrz na swoją onResumemetodę w ItemFragment:

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

to, co właśnie zaktualizowałeś przed wywołaniem, notifyDataSetChanged()nie jest polem adaptera, private List<Item> items;ale identycznie zadeklarowanym polem fragmentu. Adapter nadal przechowuje odniesienie do listy elementów przekazanych podczas tworzenia adaptera (np. W onCreate fragmentu). Najkrótszym (w sensie liczby zmian), ale niezbyt eleganckim sposobem, aby kod zachowywał się zgodnie z oczekiwaniami, jest po prostu zastąpienie linii:

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

z

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

Bardziej eleganckie rozwiązanie:

1) usuń elementy private List<Item> items;z ItemFragment- odwołanie do nich musimy zachować tylko w adapterze

2) Zmień na Utwórz do:

@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) dodaj metodę w ItemAdapter:

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

4) zmień swój onResume na:

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

Czy nie byłoby czystsze przenoszenie całego dbHelpera do adaptera? Więc tylko zadzwonisz, adapter.swapItems();a adapter zrobi dbHelper.getItems()wszystko. W każdym razie dzięki za odpowiedź :)
Ansgar

7
Dlaczego miałbyś ponownie wyczyścić () i dodać elementy? Czy to nie jest dokładnie celem notifyDataSetChanged()?
Phil Ryan

1
@tomsaz czy możesz mi pomóc z tym stackoverflow.com/questions/28148618/ ...

1
Dzięki @tomsaz Gawel, twoja zamianaItems naprawdę bardzo mi pomagają, nie wiem dlaczego mój adapter.notifydatasetchanged nie działa, ponieważ "lista", którą mijam jest również aktualizowana, nawet ja to sprawdziłem przez wydrukowanie dziennika, czy możesz mi to wyjaśnić koncepcja
Kimmi Dhingra

1
Ta odpowiedź jest poprawna. Problem polega na tym, że lista tablic pozycji ADAPTERA nie była aktualizowana. Oznacza to, że możesz wywołać powiadomienie o zmianie, aż Twoja twarz stanie się niebieska bez żadnego efektu. Adapter aktualizuje zestaw danych przy użyciu tego samego zestawu danych, więc NIE ma żadnych zmian. Inną alternatywą dla rozwiązania zamieszczonego w tej odpowiedzi, która może być czystsza, jest: adapter.items = items; adapter.notifyDataSetChanged ();
Ray Li

23

Przypisujesz ponownie załadowane elementy do elementów zmiennych globalnych w programie onResume(), ale nie będzie to odzwierciedlane w ItemAdapterklasie, ponieważ ma własną zmienną instancji o nazwie „items”.

Aby odświeżyć ListView, dodaj refresh () w ItemAdapterklasie, która akceptuje dane listy, czyli elementy

class ItemAdapter
{
    .....

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

aktualizacja za onResume()pomocą następującego kodu

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

1
Dokładnie tak. Konstruktor adaptera oczekuje, że zostaną przekazane elementy, ale zawsze aktualizuje tylko pole klasy zewnętrznej.
LuxuryMode

Cześć Santhosh. Czy możesz spojrzeć na podobny problem: stackoverflow.com/questions/35850715/ ...

8

W onResume () zmień tę linię

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

do

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

Problem polega na tym, że nigdy nie mówisz adapterowi o liście nowych elementów. Jeśli nie chcesz przekazywać nowej listy do adaptera (wydaje się, że nie), po prostu użyj items.addAllpo clear(). Zapewni to modyfikację tej samej listy, do której odwołuje się adapter.


To mylące, że adapter.clear()nie zmusza adaptera do uświadomienia sobie, że widok powinien się odświeżyć, ale adapter.add()lub tak się adapter.addAll()dzieje. Ale dzięki za odpowiedź!
w3bshark

Zauważ, że użyłem, items.addAll()a nie adapter.addAll (). Jedyną rzeczą, która pozwala adapterowi reagować na zmiany, jest plik notifyDataSetChanged. Adapter widzi w ogóle zmiany, ponieważ itemsjest to ta sama lista, której używa adapter.
Justin Breitfeller

4

Jeśli adapter jest już ustawiony, ponowne ustawienie nie spowoduje odświeżenia widoku listy. Zamiast tego najpierw sprawdź, czy listview ma adapter, a następnie wywołaj odpowiednią metodę.

Myślę, że tworzenie nowej instancji adaptera podczas ustawiania widoku listy nie jest dobrym pomysłem. Zamiast tego utwórz obiekt.

BuildingAdapter adapter = new BuildingAdapter(context);

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

Możesz także spróbować uruchomić listę odświeżania w wątku interfejsu użytkownika:

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

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

Nie jestem pewien, jak zaimplementować wątek interfejsu użytkownika. Moje główne ćwiczenie ma 3 fragmenty (zakładki), a kod w pytaniu jest powiązany z jednym z fragmentów, które zawierają widok listy. Powodem przekazywania elementów do ItemAdapterjest to, że chcę pokolorować wiersze, a widok listy wyświetla wiele elementów danych. Wysłałem kod adaptera.
Coder

Musisz umieścić swój kod, który zapełni twoją listę w moim przykładowym kodzie, używając „this”. zamiast „aktywności”
AlexGo

W niektórych przypadkach nie jest aktualizowany po uruchomieniu notifyDataSetChanged () w innym wątku, więc powyższe rozwiązanie jest właściwe w niektórych przypadkach.
Ayman Al-Absi

4

Jeśli chcesz zaktualizować swój widok listy, nie ma znaczenia, czy chcesz to zrobić w innej funkcji onResume(), onCreate()czy w innej, pierwszą rzeczą, którą musisz zdać sobie sprawę, jest to, że nie musisz tworzyć nowej instancji adaptera, po prostu wypełnij tablice z twoimi danymi ponownie. Pomysł jest podobny do tego:

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

Więc w zależności od tej odpowiedzi powinieneś użyć tego samego List<Item>w swoim Fragment. Podczas pierwszej inicjalizacji karty należy wypełnić listę elementami i ustawić adapter w widoku listy. Następnie przy każdej zmianie w elementach musisz usunąć wartości z głównego, List<Item> itemsa następnie wypełnić go ponownie nowymi elementami i zadzwonić notifySetDataChanged();.

Tak to działa : ).


Dziękuję za odpowiedź. Dokonałem zmian, jak wspomniałeś. Wysłałem swój kod. Nadal nie działa. Teraz nawet nie pokazuje widoku listy po dodaniu nowych elementów.
Coder

Zmieniłem kod. Dziwną rzeczą jest to, że element nie jest aktualizowany w DB
Coder

Ten wątek dotyczy bazy danych stackoverflow.com/questions/14555332/ ...
Coder

3

Odpowiedź od AlexGo załatwiła mi sprawę:

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

Aktualizacja listy działała wcześniej, gdy aktualizacja została wyzwolona ze zdarzenia GUI, a zatem znajdowała się w wątku interfejsu użytkownika.

Jednak gdy zaktualizuję listę z innego zdarzenia / wątku - tj. Wywołanie spoza aplikacji, aktualizacja nie pojawi się w wątku interfejsu użytkownika i zignoruje wywołanie getListView. Wywołanie aktualizacji za pomocą runOnUiThread jak powyżej załatwiło sprawę. Dzięki!!


3

Spróbuj tego

@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();

Spróbuj tego w onPause()metodzie klasy Activity.



1

Jeśli lista znajduje się w samym adapterze, należy również wywołać funkcję aktualizującą listę notifyDataSetChanged().

Uruchomienie tej funkcji z poziomu wątku interfejsu użytkownika załatwiło sprawę:

refresh()Funkcja wewnątrz zasilacza

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

Następnie z kolei uruchom tę funkcję z poziomu wątku interfejsu użytkownika

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

To rzeczywiście miało znaczenie dla mnie, ponieważ aktualizacja przeszła przez sieć za pośrednictwem innego wątku.
Chuck

0

Spróbuj tak:

this.notifyDataSetChanged();

zamiast:

adapter.notifyDataSetChanged();

Trzeba notifyDataSetChanged()się do ListViewnie do klasy adaptera.


oczywiście, że nie, jedyna szansa, jeśli aktywność zostanie rozszerzona o widok listy
cmario
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.