Вопрос

Как обновить Android ListView после добавления / удаления динамических данных?

Это было полезно?

Решение

Вызов notifyDataSetChanged() на ваше Adapter объект после изменения данных в этом адаптере.

Некоторые дополнительные сведения о том, как и когда звонить notifyDataSetChanged() можно посмотреть в это видео Google I/O.

Другие советы

Также вы можете использовать это:

myListView.invalidateViews();

Пожалуйста, игнорируйте все invalidate(), invalidateViews(), requestLayout(), ...ответы на этот вопрос.

Правильный поступок (и, к счастью, также отмеченный как правильный ответ) — это звонить notifyDataSetChanged() на вашем адаптере.

Поиск неисправностей

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

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

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

Дело в том, что notifyDataSetChanged работает только в том случае, если набор данных изменился.Так что это то место, куда стоит поискать, если вы не обнаружите происходящих изменений.Отладка при необходимости.

ArrayAdapter против BaseAdapter

Я обнаружил, что работа с адаптером, который позволяет управлять коллекцией, например с BaseAdapter, работает лучше.Некоторые адаптеры, такие как ArrayAdapter, уже управляют собственной коллекцией, что затрудняет доступ к нужной коллекции для обновлений.На самом деле в большинстве случаев это просто ненужный дополнительный уровень сложности.

Поток пользовательского интерфейса

Это правда, что это нужно вызывать из потока пользовательского интерфейса.В других ответах есть примеры того, как этого добиться.Однако это необходимо только в том случае, если вы работаете с этой информацией вне потока пользовательского интерфейса.Это из службы или потока, не связанного с пользовательским интерфейсом.В простых случаях вы будете обновлять свои данные одним нажатием кнопки или другим действием/фрагментом.Итак, все еще в потоке пользовательского интерфейса.Нет необходимости всегда вставлять этот runOnUiTrhead.

Быстрый пример проекта

Можно найти по адресу https://github.com/hanscappelle/so-2250770.git.Просто клонируйте и откройте проект в Android Studio (gradle).В этом проекте есть MainAcitivity, создающая ListView со всеми случайными данными.Этот список можно обновить с помощью меню действий.

Реализация адаптера, которую я создал для этого примера ModelObject, предоставляет сбор данных.

public class MyListAdapter extends BaseAdapter {

    /**
     * this is our own collection of data, can be anything we 
     * want it to be as long as we get the abstract methods 
     * implemented using this data and work on this data 
     * (see getter) you should be fine
     */
    private List<ModelObject> mData;

    /**
     * our ctor for this adapter, we'll accept all the things 
     * we need here
     *
     * @param mData
     */
    public MyListAdapter(final Context context, final List<ModelObject> mData) {
        this.mData = mData;
        this.mContext = context;
    }

    public List<ModelObject> getData() {
        return mData;
    }

    // implement all abstract methods here
}

Код из MainActivity

public class MainActivity extends Activity {

    private MyListAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ListView list = (ListView) findViewById(R.id.list);

        // create some dummy data here
        List<ModelObject> objects = getRandomData();
        // and put it into an adapter for the list
        mAdapter = new MyListAdapter(this, objects);
        list.setAdapter(mAdapter);

        // mAdapter is available in the helper methods below and the 
        // data will be updated based on action menu interactions

        // you could also keep the reference to the android ListView 
        // object instead and use the {@link ListView#getAdapter()} 
        // method instead. However you would have to cast that adapter 
        // to your own instance every time
    }

    /**
     * helper to show what happens when all data is new
     */
    private void reloadAllData(){
        // get new modified random data
        List<ModelObject> objects = getRandomData();
        // update data in our adapter
        mAdapter.getData().clear();
        mAdapter.getData().addAll(objects);
        // fire the event
        mAdapter.notifyDataSetChanged();
    }

    /**
     * helper to show how only changing properties of data 
     * elements also works
     */
    private void scrambleChecked(){
        Random random = new Random();
        // update data in our adapter, iterate all objects and 
        // resetting the checked option
        for( ModelObject mo : mAdapter.getData()) {
            mo.setChecked(random.nextBoolean());
        }
        // fire the event
        mAdapter.notifyDataSetChanged();
    }
}

Больше информации

Еще один хороший пост о возможностях listViews можно найти здесь: http://www.vogella.com/articles/AndroidListView/article.html

Вызовите runnable, когда захотите:

runOnUiThread(run);

OnCreate(), вы устанавливаете свой работоспособный поток:

run = new Runnable() {
    public void run() {
        //reload content
        arraylist.clear();
        arraylist.addAll(db.readAll());
        adapter.notifyDataSetChanged();
        listview.invalidateViews();
        listview.refreshDrawableState();
    }
};

у меня возникли проблемы с динамическим обновлением моего списка.

Вызовите notifyDataSetChanged() на своем адаптере.

Некоторые дополнительные сведения о том, как и когда вызывать notifyDataSetChanged(), можно посмотреть в этом видеоролике Google I/O.

notifyDataSetChanged() не работал должным образом в моем случае [я вызвал notifyDataSetChanged из другого класса].На всякий случай я отредактировал ListView в текущем действии (потоке).Это видео спасибо Кристоферу дал последнюю подсказку.

На втором занятии я использовал

Runnable run = new Runnable(){
     public void run(){
         contactsActivity.update();
     }
};
contactsActivity.runOnUiThread(run);

чтобы получить доступ к update() из моей активности.Это обновление включает в себя

myAdapter.notifyDataSetChanged();

чтобы сообщить адаптеру обновить представление.Насколько я могу сказать, работало нормально.

Если вы используете Простой адаптер курсора попробуй позвонить запрос() на объекте Курсор.

если вас все еще не устраивает обновление ListView, вы можете посмотреть этот фрагмент, он предназначен для загрузки ListView из базы данных, на самом деле вам нужно просто перезагрузить ListView после выполнения любой операции CRUD Это не лучший способ кодирования, но он обновит ListView по вашему желанию..

У меня это работает .... если вы найдете лучшее решение, пожалуйста, поделитесь...

.......
......
do your CRUD Operations..
......
.....
DBAdapter.open();
DBAdapter.insert_into_SingleList();
// Bring that DB_results and add it to list as its contents....
                                            ls2.setAdapter(new ArrayAdapter(DynTABSample.this,
    android.R.layout.simple_list_item_1,                        DBAdapter.DB_ListView));
                                            DBAdapter.close();

Решения, предложенные людьми в этом посте, работают или нет в основном в зависимости от версии Android вашего устройства.Например, чтобы использовать метод AddAll, вам необходимо поместить android:minSdkVersion="10" на свое устройство Android.

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

Мой код:Используя мой собственный класс данных RaceResult, вы используете свою собственную модель данных.

РезультатGpRowAdapter.java

public class ResultGpRowAdapter extends ArrayAdapter<RaceResult> {

    Context context;
    int resource;
    List<RaceResult> data=null;

        public ResultGpRowAdapter(Context context, int resource, List<RaceResult> objects)           {
        super(context, resource, objects);

        this.context = context;
        this.resource = resource;
        this.data = objects;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ........
        }

        //my own method to populate data           
        public void myAddAll(List<RaceResult> items) {

        for (RaceResult item:items){
            super.add(item);
        }
    }

РезультатыGp.java

public class ResultsGp extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...........
    ...........
    ListView list = (ListView)findViewById(R.id.resultsGpList); 

    ResultGpRowAdapter adapter = new ResultGpRowAdapter(this,  R.layout.activity_result_gp_row, new ArrayList<RaceResult>()); //Empty data

   list.setAdapter(adapter);

   .... 
   ....
   ....
   //LOAD a ArrayList<RaceResult> with data

   ArrayList<RaceResult> data = new ArrayList<RaceResult>();
   data.add(new RaceResult(....));
   data.add(new RaceResult(....));
   .......

   adapter.myAddAll(data); //Your list will be udpdated!!!

Если вы хотите сохранить положение прокрутки при обновлении, и вы можете это сделать:

if (mEventListView.getAdapter() == null) {
    EventLogAdapter eventLogAdapter = new EventLogAdapter(mContext, events);
    mEventListView.setAdapter(eventLogAdapter);
} else {
    ((EventLogAdapter)mEventListView.getAdapter()).refill(events);
}

public void refill(List<EventLog> events) {
    mEvents.clear();
    mEvents.addAll(events);
    notifyDataSetChanged();
}

Подробную информацию см. Просмотр списка Android:Сохраняйте положение прокрутки при обновлении.

Просто используйте myArrayList.remove(position); внутри слушателя:

  myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override public void onItemClick(AdapterView<?> parent, android.view.View view, int position, long id) {
           myArrayList.remove(position);
           myArrayAdapter.notifyDataSetChanged();
        }
    });

Вам нужно использовать один объект из этого списка, данные которого вы раздуваете. ListView.Если ссылка изменена, то notifyDataSetChanged() не работает. Всякий раз, когда вы удаляете элементы из представления списка, также удаляйте их из списка, который вы используете, будь то ArrayList<> или что-то еще, затем вызовитеnotifyDataSetChanged() на объекте вашего класса адаптера.

Итак, посмотрите, как мне это удалось в моем адаптере, см. ниже.

public class CountryCodeListAdapter extends BaseAdapter implements OnItemClickListener{

private Context context;
private ArrayList<CountryDataObject> dObj;
private ViewHolder holder;
private Typeface itemFont;
private int selectedPosition=-1;
private ArrayList<CountryDataObject> completeList;

public CountryCodeListAdapter(Context context, ArrayList<CountryDataObject> dObj) {
    this.context = context;
    this.dObj=dObj;
    completeList=new  ArrayList<CountryDataObject>();
    completeList.addAll(dObj);
    itemFont=Typeface.createFromAsset(context.getAssets(), "CaviarDreams.ttf");
}

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

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

@Override
public long getItemId(int position) {
    return position;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
    if(view==null){
        holder = new ViewHolder();
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(R.layout.states_inflator_layout, null);
        holder.textView = ((TextView)view.findViewById(R.id.stateNameInflator));
        holder.checkImg=(ImageView)view.findViewById(R.id.checkBoxState);
        view.setTag(holder);
    }else{
        holder = (ViewHolder) view.getTag();
    }
    holder.textView.setText(dObj.get(position).getCountryName());
    holder.textView.setTypeface(itemFont);

    if(position==selectedPosition)
     {
         holder.checkImg.setImageResource(R.drawable.check);
     }
     else
     {
         holder.checkImg.setImageResource(R.drawable.uncheck);
     }
    return view;
}
private class ViewHolder{
    private TextView textView;
    private ImageView checkImg;
}

public void getFilter(String name) {
    dObj.clear();
    if(!name.equals("")){
    for (CountryDataObject item : completeList) {
        if(item.getCountryName().toLowerCase().startsWith(name.toLowerCase(),0)){
            dObj.add(item);
        }
    }
    }
    else {
        dObj.addAll(completeList);
    }
    selectedPosition=-1;
    notifyDataSetChanged();
    notifyDataSetInvalidated(); 
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
        long id) {
    Registration reg=(Registration)context;
    selectedPosition=position;
    reg.setSelectedCountryCode("+"+dObj.get(position).getCountryCode());
    notifyDataSetChanged();
}
}

После удаления данных из списка вам необходимо позвонить refreshDrawableState().Вот пример:

final DatabaseHelper db = new DatabaseHelper (ActivityName.this);

db.open();

db.deleteContact(arg3);

mListView.refreshDrawableState();

db.close();

и deleteContact метод в DatabaseHelper класс будет чем-то похож

public boolean deleteContact(long rowId) {

   return db.delete(TABLE_NAME, BaseColumns._ID + "=" + rowId, null) > 0;

}

Мне не удалось заставить notifyDataSetChanged() работать над обновлением моего SimpleAdapter, поэтому вместо этого я попытался сначала удалить все представления, которые были прикреплены к родительскому макету, с помощью removeAllViews(), затем добавить ListView, и это сработало, позволив мне обновить Пользовательский интерфейс:

LinearLayout results = (LinearLayout)findViewById(R.id.results);
ListView lv = new ListView(this);
ArrayList<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>();
SimpleAdapter adapter = new SimpleAdapter( this, list, R.layout.directory_row, 
                new String[] { "name", "dept" }, new int[] { R.id.name, R.id.dept } );

for (...) { 
    HashMap<String, String> map = new HashMap<String, String>();
    map.put("name", name);
    map.put("dept", dept);
    list.add(map);
}

lv.setAdapter(adapter);
results.removeAllViews();     
results.addView(lv);

Для меня после изменения информации в базе данных sql ничто не могло обновить представление списка (а точнее, представление расширяемого списка), поэтому, если notifyDataSetChanged() не помогает, вы можете сначала попытаться очистить свой список и добавить его снова после этого вызова notifyDataSetChanged().Например

private List<List<SomeNewArray>> arrayList;
List<SomeNewArray> array1= getArrayList(...);
List<SomeNewArray> array2= getArrayList(...);
arrayList.clear();
arrayList.add(array1);
arrayList.add(array2);
notifyDataSetChanged();

Надеюсь, это имеет смысл для вас.

при использовании SimpleCursorAdapter можно вызвать изменениеCursor(newCursor) на адаптере.

Я был таким же, когда во фрагменте я хотел заполнить ListView (в одном TextView) MAC-адресами устройств BLE, сканированных за некоторое время.

Я сделал следующее:

public class Fragment01 extends android.support.v4.app.Fragment implements ...
{
    private ListView                listView;
    private ArrayAdapter<String>    arrayAdapter_string;

...

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    ...
    this.listView= (ListView) super.getActivity().findViewById(R.id.fragment01_listView);
    ...
    this.arrayAdapter_string= new ArrayAdapter<String>(super.getActivity(), R.layout.dispositivo_ble_item, R.id.fragment01_item_textView_titulo);
    this.listView.setAdapter(this.arrayAdapter_string);
}


@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
{
    ...
    super.getActivity().runOnUiThread(new RefreshListView(device));
}


private class RefreshListView implements Runnable
{
    private BluetoothDevice bluetoothDevice;

    public RefreshListView(BluetoothDevice bluetoothDevice)
    {
        this.bluetoothDevice= bluetoothDevice;
    }

    @Override
    public void run()
    {
        Fragment01.this.arrayAdapter_string.add(new String(bluetoothDevice.toString()));
        Fragment01.this.arrayAdapter_string.notifyDataSetChanged();
    }
}

Затем ListView начал динамически заполняться MAC-адресами найденных устройств.

Я думаю, это зависит от того, что вы подразумеваете под обновлением.Вы имеете в виду, что отображение графического интерфейса должно быть обновлено, или вы имеете в виду, что дочерние представления должны быть обновлены, чтобы вы могли программно вызвать getChildAt(int) и получить представление, соответствующее тому, что находится в адаптере.

Если вы хотите обновить отображение графического интерфейса, вызовите notifyDataSetChanged() на адаптере.Графический интерфейс будет обновлен при следующей перерисовке.

Если вы хотите иметь возможность вызвать getChildAt(int) и получить представление, отражающее содержимое адаптера, вызовите метод LayoutChildren().Это приведет к восстановлению дочернего представления на основе данных адаптера.

У меня был ArrayList, который я хотел отобразить в виде списка.ArrayList содержал элементы из MySQL.Я переопределил метод onRefresh и в этом методе использовал tablelayout.removeAllViews(); а затем повторил процесс получения данных из базы данных.Но перед этим обязательно очистите свой ArrayList или любую другую структуру данных, иначе новые данные будут добавлены к старым.

Если вы хотите обновить представление списка пользовательского интерфейса из службы, сделайте адаптер статическим в своем основном действии и сделайте следующее:

@Override
public void onDestroy() {
    if (MainActivity.isInFront == true) {
        if (MainActivity.adapter != null) {
            MainActivity.adapter.notifyDataSetChanged();
        }

        MainActivity.listView.setAdapter(MainActivity.adapter);
    }
}    

Если вы следуете направляющим линиям Android и используете ContentProviders чтобы получить данные от Database и вы показываете это в ListView используя CursorLoader и CursorAdapters , тогда все изменения в связанных данных будут автоматически отражены в ListView.

Твой getContext().getContentResolver().notifyChange(uri, null); на курсоре в ContentProvider будет достаточно, чтобы отразить изменения. Никаких дополнительных действий не требуется.

Но если вы не используете все это, вам нужно сообщить адаптеру, когда набор данных меняется.Также вам необходимо повторно заполнить/перезагрузить набор данных (скажем, список), а затем вам нужно позвонить notifyDataSetChanged() на адаптере.

notifyDataSetChanged()не будет работать, если в наборе данных нет изменений.Вот комментарий над методом в документации:

/**
 * Notifies the attached observers that the underlying data has been changed
 * and any View reflecting the data set should refresh itself.
 */

Мне удалось получить notifyDataSetChanged только путем получения новых данных адаптера, затем сброса адаптера для представления списка, а затем выполнения вызова следующим образом:

    expandableAdapter = baseFragmentParent.setupEXLVAdapter();
    baseFragmentParent.setAdapter(expandableAdapter);
    expandableAdapter.notifyDataSetChanged(); 

по другому варианту onWindowFocusChanged метод, но уверен, что он чувствителен и требует дополнительного кодирования для тех, кто заинтересован

 override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)

       // some controls needed
        programList = usersDBHelper.readProgram(model.title!!)
        notesAdapter = DailyAdapter(this, programList)
        notesAdapter.notifyDataSetChanged()
        listview_act_daily.adapter = notesAdapter
    }

Предположим, вы передали список своему адаптеру.
Использовать:

list.getAdapter().notifyDataSetChanged()

чтобы обновить свой список.

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

Самое классное было в том, что я использовал не recycler view, а простой list view, и этот list view инициализировался в классе adapter.Итак, вызывая notifyDataSetChanged() ничего не будет делать внутри класса adapter и даже в классе activity, где инициализирован объект adapter, потому что метод delete был в классе adapter.

Итак, решение состояло в том, чтобы удалить объект из адаптера в классе adapter getView метод (для удаления только этого конкретного объекта, но если вы хотите удалить все, вызовите clear()).

Чтобы вы получили некоторое представление о том, как выглядел мой код,

public class WordAdapter extends ArrayAdapter<Word> {
  Context context;

  public WordAdapter(Activity context, ArrayList<Word> words) {}
    //.......

    @NonNull
    @Override
    public View getView(final int position, View convertView, ViewGroup group) {
      //.......
     ImageButton deleteBt = listItemView.findViewById(R.id.word_delete_bt);
     deleteBt.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             if (vocabDb.deleteWord(currentWord.id)) {
                //.....
             } else{
                //.....
             }
             remove(getItem(position)); // <---- here is the trick ---<
             //clear() // if you want to clear everything
         }
    });
 //....

Примечание:здесь remove() и getItem() методы наследуются от класса Adapter.

  • remove() - чтобы удалить конкретный элемент, на который был сделан щелчок
  • getItem(position) - заключается в том, чтобы получить элемент (здесь, это мой Word object , который я добавил в список) из выбранной позиции.

Вот как я устанавливаю адаптер в listview в классе activity,

    ArrayList<Word> wordList = new ArrayList();
    WordAdapter adapter = new WordAdapter(this, wordList);

    ListView list_view = (ListView) findViewById(R.id.activity_view_words);
    list_view.setAdapter(adapter);

Самый простой — просто сделать новый Адапер и удалить старый:

myListView.setAdapter(new MyListAdapter(...));
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top