it-roy-ru.com

Как обновить список рассылки Android?

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

407
UMAR

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

Некоторые дополнительные подробности о том, как/когда вызывать функцию notifyDataSetChanged(), можно посмотреть в это видео ввода/вывода Google .

509
Christopher Orr

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

myListView.invalidateViews();
197
Bobs

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

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

Поиск проблемы

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

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

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

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

ArrayAdapter против BaseAdapter

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

UI Thread

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

147
hcpl

Позвоните в любой момент, когда захотите:

runOnUiThread(run);

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

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

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

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

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

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

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

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

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

myAdapter.notifyDataSetChanged();

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

11
Wille

Если вы используете SimpleCursorAdapter попробуйте вызвать Requery () для объекта Cursor.

9
freehacker

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

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

.......
......
 выполнять ваши операции CRUD ..
......
.....
 DBAdapter. open (); 
 DBAdapter.insert_into_SingleList (); 
 // Принесите этот DB_results и добавьте его в список как его содержимое ....
 ls2.setAdapter (новый ArrayAdapter (DynTABSample.this, 
 Android.R.layout.simple_list_item_1, DBAdapter.DB_ListView)); 
 DBAdapter.close (); 
5
Nandagopal T

Решения, предложенные людьми в этом посте, работают или нет, в основном, в зависимости от версии 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!!!
2
AntuanSoft

Просто используйте 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();
        }
    });
1
Steffo Dimfelt

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

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: Сохраняйте позицию прокрутки при обновлении .

1
Brandon Yang

Вам нужно использовать один объект из этого списка, который содержит данные, которые вы надуваете ListView. Если ссылка изменяется, то notifyDataSetChanged() не работает. Когда вы удаляете элементы из представления списка, также удаляйте их из списка, который вы используете, будь то ArrayList <> или что-то еще, тогда Call 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();
}
}
1
ADM

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

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

        MainActivity.listView.setAdapter(MainActivity.adapter);
    }
}    
0
Mayank Saini

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

0
Kshitij G

Для меня после изменения информации в базе данных 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();

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

0
Aivus

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

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

Таким образом, решение состояло в том, чтобы удалить объект из адаптера в методе класса 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 , который я добавил в список) из позиции, по которой щелкнули.

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

    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);
0
Blasanka

Если вы идете по направляющим линиям 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.
 */
0
Sanjeet A

Я был таким же, когда во фрагменте я хотел заполнить 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-адресами найденных устройств.

0
jsanmarb

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

0
Vihaan Verma

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

list.getAdapter().notifyDataSetChanged()

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

0
Mahdi-Malv

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

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);
0
Chasden

другой вариант - метод 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
    }
0
Sam

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

    expandableAdapter = baseFragmentParent.setupEXLVAdapter();
    baseFragmentParent.setAdapter(expandableAdapter);
    expandableAdapter.notifyDataSetChanged(); 
0
Kristy Welsh

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

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

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

0
dazed

После удаления данных из списка необходимо вызвать 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;

}
0
user1031852