Android ListView не обновляется после notifyDataSetChanged

116

Мой код 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);
        }
    }
}

Мой 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>

Но это не обновляет ListView. Даже после перезапуска приложения обновленные элементы не отображаются. Мой ItemAdapterпростираетсяBaseAdapter

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

Кто-нибудь может помочь?

кодировщик
источник
2
Вы проверяли, правильно ли работает работа с базой данных? Как выглядит адаптер? Кроме того, если вы создаете объект on для adapterссылки, почему вы проверяете его на null на одну строку ниже?
Luksprog
Код не генерирует исключение, и я проверил его с помощью отладки. Все методы выполнены без ошибок. Да, это глупая ошибка.
Coder

Ответы:

229

Посмотрите на свой onResumeметод в ItemFragment:

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

то, что вы только что обновили перед вызовом, notifyDataSetChanged()является не полем адаптера, private List<Item> items;а идентично объявленным полем фрагмента. Адаптер по-прежнему хранит ссылку на список элементов, которые вы передали при создании адаптера (например, во фрагменте onCreate). Самый короткий (в смысле количества изменений), но не элегантный способ заставить ваш код вести себя так, как вы ожидаете, - это просто заменить строку:

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

с участием

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

Более элегантное решение:

1) удалить элементы private List<Item> items;из ItemFragment- нам нужно сохранить ссылку на них только в адаптере

2) измените onCreate на:

@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) добавить метод в ItemAdapter:

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

4) измените свой onResume на:

@Override
public void onResume() {
    super.onResume();
    adapter.swapItems(dbHelper.getItems());
}
Томаш Гавел
источник
Разве не было бы чище переместить всю вещь dbHelper в адаптер? Таким образом, вы бы только позвонили, adapter.swapItems();а адаптер сделает dbHelper.getItems()все. Но в любом случае спасибо за ответ :)
Ансгар
7
Почему вам нужно очистить () и снова добавить элементы? Разве это не цель notifyDataSetChanged()?
Фил Райан,
1
@tomsaz, вы можете мне помочь с этим stackoverflow.com/questions/28148618/…
1
Спасибо @tomsaz Gawel, ваши swapItems действительно мне очень помогают, я не знаю, почему мой адаптер .notifydatasetchanged не работает, так как "список", который я передаю, также обновляется, даже я проверил его, распечатав журнал. Не могли бы вы объяснить мне это концепт
Кимми Дхингра
1
Это правильный ответ. Проблема в том, что список массивов элементов АДАПТЕРА не обновлялся. Это означает, что вы можете вызывать notifydatasetchanged до тех пор, пока ваше лицо не станет синим без какого-либо эффекта. Адаптер обновляет ваш набор данных тем же набором данных, поэтому НЕТ изменений. Другой альтернативой решению, опубликованному в этом ответе, которое могло бы быть более чистым, является: adapter.items = items; adapter.notifyDataSetChanged ();
Рэй Ли
23

Вы назначаете перезагруженные элементы глобальным переменным items onResume(), но это не отразится на ItemAdapterклассе, потому что у него есть собственная переменная экземпляра с именем «items».

Для обновления ListViewдобавьте в ItemAdapterкласс refresh (), который принимает данные списка, т.е. элементы

class ItemAdapter
{
    .....

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

обновить onResume()с помощью следующего кода

@Override
public void onResume()
{
    super.onResume();
    items.clear();
    items = dbHelper.getItems(); //reload the items from database
    **adapter.refresh(items);**
}
Santhosh
источник
1
Совершенно верно. Конструктор адаптера ожидает передачи элементов, но он обновляет только поле внешнего класса.
LuxuryMode 03
Привет, Сантош. Можете ли вы взглянуть на аналогичную проблему: stackoverflow.com/questions/35850715/…
8

В onResume () измените эту строку

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

в

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

Проблема в том, что вы никогда не сообщаете своему адаптеру список новых элементов. Если вы не хотите передавать новый список вашему адаптеру (а кажется, что это не так), просто используйте items.addAllпосле вашего clear(). Это гарантирует, что вы изменяете тот же список, на который ссылается адаптер.

Джастин Брайтфеллер
источник
Это сбивает с толку то, adapter.clear()что не заставляет адаптер понимать, что представление должно обновляться, но adapter.add()или adapter.addAll()делает. Спасибо за ответ!
w3bshark
Обратите внимание, что я использовал, items.addAll()а не адаптер .addAll (). Единственное, что позволяет адаптеру реагировать на изменения, - это расширение notifyDataSetChanged. Причина, по которой адаптер вообще видит изменения, - itemsэто тот же список, что и адаптер.
Джастин Брайтфеллер,
4

Если адаптер уже настроен, повторная установка не приведет к обновлению списка. Вместо этого сначала проверьте, есть ли у listview адаптер, а затем вызовите соответствующий метод.

Я думаю, что создавать новый экземпляр адаптера при настройке представления списка - не очень хорошая идея. Вместо этого создайте объект.

BuildingAdapter adapter = new BuildingAdapter(context);

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

Также вы можете попробовать запустить список обновления в потоке пользовательского интерфейса:

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

              // for example    
              adapter.add(new Object());
              adapter.notifyDataSetChanged()  
        }
});
AlexGo
источник
Я не уверен, как реализовать поток пользовательского интерфейса. Моя основная деятельность состоит из 3 фрагментов (вкладок), а код в вопросе связан с одним из фрагментов, содержащих представление списка. Причина передачи элементов в ItemAdapterтом, что я хочу раскрасить строки, а в представлении списка отображается несколько элементов данных. Я разместил код адаптера.
Coder
Вам нужно поместить свой код, который заполняет ваш список, в моем примере кода, используя this. вместо «активность»
AlexGo
В некоторых случаях он не обновляется, когда вы запускаете notifyDataSetChanged () в другом потоке, поэтому приведенное выше решение подходит для некоторых случаев.
Айман Аль-Абси
4

Если вы хотите обновить ваш ListView не имеет значения , если вы хотите сделать это на onResume(), onCreate()или в какой - либо другой функции, первая вещь , которую вы должны понять, что вам не нужно будет создать новый экземпляр адаптера, просто Заселите снова массивы с вашими данными. Идея примерно такая:

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

Поэтому в зависимости от этого ответа вы должны использовать то же самое List<Item>в своем Fragment. При первой инициализации адаптера вы заполняете список элементами и устанавливаете адаптер для просмотра списка. После этого при каждом изменении в ваших элементах вы должны очищать значения из основного, List<Item> itemsа затем снова заполнять его своими новыми элементами и вызывать notifySetDataChanged();.

Вот как это работает :).

h4rd4r7c0r3
источник
Спасибо за ответ. Я внес изменения, как вы упомянули. Я разместил свой код. Это все еще не работает. Теперь он даже не отображает список при добавлении новых элементов.
Coder
Я изменил код. Странно наблюдать, что элемент не обновляется в БД
Coder
Этот поток предназначен для базы данных stackoverflow.com/questions/14555332/…
Coder
3

Ответ AlexGo помог мне:

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

Обновление списка работало для меня раньше, когда обновление запускалось из события графического интерфейса пользователя, таким образом, находясь в потоке пользовательского интерфейса.

Однако, когда я обновляю список из другого события / потока, т. Е. Вызова извне приложения, обновления не будет в потоке пользовательского интерфейса, и он проигнорирует вызов getListView. Вызов обновления с помощью runOnUiThread, как указано выше, помог мне. Спасибо!!

user2996950
источник
3

Попробуй это

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

Попробуйте это в onPause()методе класса Activity.

Som
источник
1
adapter.setNotifyDataChanged()

должен сделать свое дело.

наемный убийца
источник
3
куда ставить вот вопрос ??
SwiftBoy
1

Если ваш список содержится в самом адаптере, следует также вызвать функцию, обновляющую список notifyDataSetChanged().

Запуск этой функции из потока пользовательского интерфейса помог мне:

refresh()Функции внутри адаптера

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

Затем, в свою очередь, запустите эту функцию из потока пользовательского интерфейса

getActivity().runOnUiThread(new Runnable() { 
    @Override
    public void run() {
          adapter.refresh()  
    }
});
Деван Кутзи
источник
Это действительно имело для меня значение, поскольку обновление пришло по сети через другой поток.
Чак
0

Попробуйте вот так:

this.notifyDataSetChanged();

вместо того:

adapter.notifyDataSetChanged();

Вы должны notifyDataSetChanged()в ListViewне к классу адаптера.

Jachu
источник
конечно, не будет, единственный шанс, если активность будет расширена
списком