Как обновить Android listview?

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

25 ответов


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

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


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

myListView.invalidateViews();

наслаждайтесь!


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

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

устранение неисправностей

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

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

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

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

ArrayAdapter vs BaseAdapter

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

UI Thread

это верно, что это должно быть вызвано из потока UI. Другие ответы содержат примеры того, как этого достичь. Однако это требуется только в том случае, если вы работаете над этой информацией вне потока пользовательского интерфейса. Это из службы или не потока пользовательского интерфейса. В простых случаях вы будете обновлять свои данные с помощью нажатия кнопки или другого действия / фрагмента. Так что все еще в потоке UI. Не нужно всегда вставлять этот 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();
    }
};

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

вызов notifyDataSetChanged() на ваш адаптер.

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

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

во втором классе я использовал

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

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

myAdapter.notifyDataSetChanged();

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


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


Если вы все еще не удовлетворены обновлением 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-устройство.

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

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

ResultGpRowAdapter.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);
        }
    }

ResultsGp.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 ListView: сохранить положение прокрутки при обновлении.


просто использовать 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 можно вызвать changeCursor (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-адресом найденных устройств.


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

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

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


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


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

@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()не будет работать, если нет изменений в наборе данных. Вот комментарий выше метода в docs -

/**
 * 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, а простое представление списка и это представление списка, инициализированное в классе адаптера. Итак, вызывая notifyDataSetChanged() ничего не будет делать внутри класса адаптера и даже в классе activity, где объект адаптера инициализируется, поскольку метод delete был в классе адаптера.

Итак, решение состояло в том, чтобы удалить объект из адаптера в классе адаптера 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() - to удалите конкретный элемент, который нажат
  • getItem(position) - это получить элемент(здесь, это мой объект Word что я добавил в список) из нажатой позиции.

вот как я установил адаптер в 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(...));