Как обновить Android ListView
после добавления/удаления динамических данных?
Как обновить список просмотров Android?
Ответ 1
Вызовите notifyDataSetChanged()
на свой объект Adapter
после изменения данных в этом адаптере.
Некоторые дополнительные особенности того, как/когда вызывать notifyDataSetChanged()
, можно просмотреть в этот Google Видео ввода/вывода.
Ответ 2
Также вы можете использовать это:
myListView.invalidateViews();
Ответ 3
Пожалуйста, не requestLayout()
внимания на все ответы 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
Ответ 4
Вызовы выполняются всякий раз, когда вы хотите:
runOnUiThread(run);
OnCreate()
, вы устанавливаете свой runnable thread:
run = new Runnable() {
public void run() {
//reload content
arraylist.clear();
arraylist.addAll(db.readAll());
adapter.notifyDataSetChanged();
listview.invalidateViews();
listview.refreshDrawableState();
}
};
Ответ 5
У меня возникли проблемы с динамическим обновлением моего списка.
Вызовите notifyDataSetChanged() для вашего адаптера.
Некоторые дополнительные особенности того, как/когда вызывать notifyDataSetChanged() можно просмотреть в этом видеообъявлении Google I/O.
notifyDataSetChanged() не работает должным образом в моем случае [я вызвал notifyDataSetChanged из другого класса]. Как раз в том случае, когда я редактировал ListView в текущей Activity (Thread). Это видео благодаря Кристоферу дало окончательный намек.
В моем втором классе я использовал
Runnable run = new Runnable(){
public void run(){
contactsActivity.update();
}
};
contactsActivity.runOnUiThread(run);
чтобы получить доступ к обновлению() из моей активности. Это обновление включает
myAdapter.notifyDataSetChanged();
чтобы сообщить адаптеру, чтобы обновить представление. Насколько я могу судить, работал отлично.
Ответ 6
Если вы используете SimpleCursorAdapter, попробуйте вызвать requery() в объекте курсора.
Ответ 7
если вы еще не удовлетворены ListView Refreshment, вы можете посмотреть этот фрагмент, это для загрузки listView из базы данных. Фактически, вам нужно просто перезагрузить ListView после выполнения любой операции CRUD Это не лучший способ кодирования, но он обновит ListView по вашему желанию.
Он работает для меня.... если u найдет лучшее решение, пожалуйста, поделитесь...
....... ...... 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();
Ответ 8
Решения, предлагаемые людьми в этом сообщении, работают или не зависят в основном от версии вашего устройства Android. Например, чтобы использовать метод AddAll, вы должны поместить в Android-устройство android: minSdkVersion = "10".
Чтобы решить эти вопросы для всех устройств, я создал свой собственный метод в своем адаптере и использование внутри метода 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!!!
Ответ 9
Если вы хотите сохранить свою позицию прокрутки во время обновления, и вы можете сделать это:
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: поддерживайте свою позицию прокрутки при обновлении.
Ответ 10
Вам нужно использовать один объект из этого списка, который содержит данные, которые вы надуваете в 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();
}
}
Ответ 11
Просто используйте 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();
}
});
Ответ 12
После удаления данных из вида списка вы должны вызвать 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;
}
Ответ 13
Если вы хотите обновить представление списка пользовательских интерфейсов от службы, сделайте адаптер статическим в своей основной деятельности и выполните следующее:
@Override
public void onDestroy() {
if (MainActivity.isInFront == true) {
if (MainActivity.adapter != null) {
MainActivity.adapter.notifyDataSetChanged();
}
MainActivity.listView.setAdapter(MainActivity.adapter);
}
}
Ответ 14
Для меня после изменения информации в базе данных 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();
Надеюсь, это имеет смысл для вас.
Ответ 15
Мне не удалось получить 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);
Ответ 16
при использовании SimpleCursorAdapter можно вызвать changeCursor (newCursor) на адаптере.
Ответ 17
Я был тем же самым, когда в фрагменте я хотел заполнить 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-адрес найденных устройств.
Ответ 18
Я думаю, это зависит от того, что вы подразумеваете под обновлением. Вы имеете в виду, что дисплей графического интерфейса должен быть обновлен, или вы имеете в виду, что дочерние представления должны быть обновлены, так что вы можете программно вызвать getChildAt (int) и получить представление, соответствующее тому, что находится в адаптере.
Если вы хотите обновить графический интерфейс GUI, а затем вызовите notifyDataSetChanged() на адаптере. GUI будет обновлен при следующем перерисовании.
Если вы хотите иметь возможность вызвать getChildAt (int) и получить представление, отражающее то, что находится в адаптере, затем вызвать layoutChildren(). Это приведет к восстановлению дочернего представления из данных адаптера.
Ответ 19
У меня был ArrayList, который я хотел отобразить в списке. ArrayList содержит элементы из mysql. Я переопределил метод Refresh, и в этом методе я использовал tablelayout.removeAllViews();, а затем повторил процесс получения данных снова из базы данных. Но перед этим обязательно очистите ArrayList или любую структуру данных, иначе новые данные будут добавлены к старой версии.
Ответ 20
Если вы идете по линиям проводников 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.
*/
Ответ 21
Мне удалось получить notifyDataSetChanged только путем получения новых данных адаптера, затем сброса адаптера для представления списка, а затем сделать вызов следующим образом:
expandableAdapter = baseFragmentParent.setupEXLVAdapter();
baseFragmentParent.setAdapter(expandableAdapter);
expandableAdapter.notifyDataSetChanged();
Ответ 22
Другой вариант - метод 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
}
Ответ 23
Представьте, что вы передали список своему адаптеру.
Использование:
list.getAdapter().notifyDataSetChanged()
обновить свой список.
Ответ 24
Если я говорил о моем сценарии здесь, ни один из приведенных выше ответов не будет работать, потому что у меня была активность, которая отображала список значений в БД вместе с кнопкой удаления, и когда была нажата кнопка удаления, я хотел удалить этот элемент из списка.
Круто было то, что я использовал не представление рециркулятора, а простое представление списка, и это представление списка инициализировалось в классе адаптера. Таким образом, вызов 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);
Ответ 25
Самый простой - просто создать новый Adaper и удалить старый:
myListView.setAdapter(new MyListAdapter(...));