Android ListView не оновлюється після повідомленняDataSetChanged


116

Код мого списку

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
Ви перевірили, чи правильно працює база даних? Як виглядає адаптер? Крім того, якщо ви створюєте об'єкт для adapterпосилання, чому ви перевіряєте його на нуль на один рядок нижче?
Лукспрог

Код не кидає виключення, і я перевірив за допомогою налагодження. Всі методи виконуються без помилок. Так, це дурна помилка.
Кодер

Відповіді:


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(). Але все одно дякую за відповідь :)
Ansgar

7
Чому вам слід очистити () та додати елементи ще раз? Це не саме мета notifyDataSetChanged()?
Філ Райан

1
@tomsaz ви можете допомогти мені з цим stackoverflow.com/questions/28148618 / ...

1
Дякую @tomsaz Gawel, ваші swapItems дуже допомагають мені, я не знаю, чому мій адаптер.notifydatasetchanged не працює, оскільки "список", який я проходжу, також оновлюється, навіть я перевірив це друком журналу, чи можете ви поясніть мені це концепція
Кіммі Дінгра

1
Ця відповідь правильна. Проблема полягає в тому, що ArrayList пункту ADAPTER не оновлювався. Це означає, що ви можете зателефонувати повідомлення змінити зміни, поки ваше обличчя не стане синім без жодного ефекту. Адаптер оновлює ваш набір даних тим самим набором даних, щоб не було змін. Інша альтернатива рішення, розміщеному в цій відповіді, яке може бути чистішим, є: adapter.items = items; adapter.notifyDataSetChanged ();
Рей Лі

23

Ви призначаєте перезавантажені елементи в глобальних змінних елементах в onResume(), але це не відображатиметься в ItemAdapterкласі, оскільки у нього є своя змінна екземпляр, що називається "items".

Для оновлення ListViewдодайте refresh () у ItemAdapterклас, який приймає дані списку, тобто елементи

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

1
Це абсолютно правильно. Конструктор адаптера розраховує на передачу елементів, але він лише коли-небудь оновлює поле зовнішнього класу.
LuxuryMode

Привіт Сантош. Ви можете поглянути на аналогічною проблемою: 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()а не adapter.addAll (). Єдине, що дозволяє адаптеру реагувати на зміни - це notifyDataSetChanged. Причина, що адаптер бачить зміни взагалі - itemsце той самий список, який використовує адаптер.
Джастін Брейтфеллер

4

Якщо адаптер уже встановлений, його встановлення знову не оновить перегляд списку. Натомість спочатку перевірте, чи є у перегляду списку адаптер, а потім зателефонуйте у відповідний метод.

Я думаю, що це не дуже гарна ідея створити новий примірник адаптера під час встановлення списку списку. Натомість створіть об’єкт.

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

Я не впевнений, як реалізувати потік інтерфейсу користувача. У моїй основній діяльності є 3 фрагменти (вкладки), а код у запитанні пов'язаний з одним із фрагментів, що містить перегляд списку. Причиною передачі елементів ItemAdapterє те, що я хочу розфарбувати рядки, а у переліку списку відображається кілька елементів даних. Я опублікував код для адаптера.
Кодер

Потрібно ввести код, який заповнює ваш список, у моєму прикладі коду, використовуючи "це". замість "діяльності"
AlexGo

У деяких випадках він не оновлюється, коли ви запускаєте notifyDataSetChanged () в різних потоках, тому вищевказане рішення є правильним для деяких випадків.
Айман Аль-Абсі

4

Якщо ви хочете оновити список перегляду, не має значення, чи хочете ви це робити 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();.

Ось як це працює:).


Дякую за відповідь. Я зробив зміни, як ви згадали. Я опублікував свій код. Це все ще не працює. Тепер він навіть не показує перегляд списку, коли нові елементи додаються.
Кодер

Я змінив код. Дивна річ, що слід спостерігати, що елемент не оновлюється в БД
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);
        }
});

Оновлення списку працювало для мене раніше, коли оновлення було запущено з події GUI, таким чином, було в потоці інтерфейсу користувача.

Однак, коли я оновлюю список з іншої події / потоку - тобто дзвінка поза додатком, оновлення не буде в потоці інтерфейсу, і воно ігнорувало виклик getListView. Виклик оновлення за допомогою runOnUiThread, як зазначено вище, зробив для мене трюк. Дякую!!


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()методі заняття.



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не для адаптера".


Звичайно, це не буде, єдиний шанс, якщо діяльність буде продовжена
списком перегляду
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.