Comment actualiser une Android ListView
après avoir ajouté/supprimé des données dynamiques?
Appelez notifyDataSetChanged()
sur votre objet Adapter
une fois que vous avez modifié les données de cet adaptateur.
Certaines informations supplémentaires sur la manière et le moment d'appeler notifyDataSetChanged()
peuvent être visualisées dans cette vidéo Google I/O .
Aussi, vous pouvez utiliser ceci:
myListView.invalidateViews();
invalidate()
, invalidateViews()
, requestLayout()
, ... à cette question.La bonne chose à faire (et heureusement aussi marquée comme bonne réponse) est appeler notifyDataSetChanged()
sur votre adaptateur .
Si appeler notifyDataSetChanged()
ne fonctionne pas, toutes les méthodes de mise en page ne vous aideront pas non plus. Croyez-moi, la ListView
a été correctement mise à jour. Si vous ne trouvez pas la différence, vous devez vérifier d’où proviennent les données de votre adaptateur.
S'il ne s'agit que d'une collection que vous gardez en mémoire, vérifiez que vous avez réellement supprimé ou ajouté le ou les éléments à la collection avant d'appeler la notifyDataSetChanged()
.
Si vous travaillez avec une base de données ou un service backend, vous devrez appeler la méthode pour récupérer les informations (ou manipuler les données en mémoire) avant d'appeler le notifyDataSetChanged()
.
Le problème est que notifyDataSetChanged
ne fonctionne que si le jeu de données a été modifié. C’est donc l’endroit à regarder si vous ne trouvez pas les changements à venir. Déboguer si nécessaire.
J'ai constaté que l'utilisation d'un adaptateur qui vous permet de gérer la collection, comme un BaseAdapter, fonctionne mieux. Certains adaptateurs comme ArrayAdapter gèrent déjà leur propre collection, ce qui rend plus difficile l'obtention de la collection appropriée pour les mises à jour. C'est vraiment juste une couche de difficulté supplémentaire inutile dans la plupart des cas.
Il est vrai que cela doit être appelé à partir du thread d'interface utilisateur. D'autres réponses ont des exemples sur la façon d'y parvenir. Toutefois, cela n'est requis que si vous travaillez sur ces informations en dehors du thread d'interface utilisateur. Cela provient d'un service ou d'un thread non UI. Dans des cas simples, vous mettrez à jour vos données à partir d'un clic de bouton ou d'une autre activité/fragment. Donc, toujours dans le fil de l'interface utilisateur. Pas besoin de toujours insérer ce runOnUiTrhead dans.
Peut être trouvé à https://github.com/hanscappelle/so-2250770.git . Il suffit de cloner et d’ouvrir le projet dans Android Studio (gradle). Ce projet a une MainAcitivity créant une ListView
avec toutes les données aléatoires. Cette liste peut être actualisée en utilisant le menu Actions.
L'implémentation d'adaptateur que j'ai créée pour cet exemple ModelObject expose la collecte de données.
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
}
Code de la 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();
}
}
Un autre article de Nice sur le pouvoir de listViews se trouve ici: http://www.vogella.com/articles/AndroidListView/article.html
Appelez exécutable quand vous le souhaitez:
runOnUiThread(run);
OnCreate()
, vous définissez votre thread exécutable:
run = new Runnable() {
public void run() {
//reload content
arraylist.clear();
arraylist.addAll(db.readAll());
adapter.notifyDataSetChanged();
listview.invalidateViews();
listview.refreshDrawableState();
}
};
j'ai eu quelques problèmes avec l'actualisation dynamique de mon listview.
Appelez notifyDataSetChanged () sur votre adaptateur.
Quelques détails supplémentaires sur comment et quand appeler notifyDataSetChanged () peuvent être visualisés dans cette vidéo Google I/O.
notifyDataSetChanged () ne fonctionnait pas correctement dans mon cas [j'ai appelé le notifyDataSetChanged depuis une autre classe]. Juste dans le cas où j'ai édité le ListView dans l'activité en cours d'exécution (Thread). Cette vidéo grâce à Christopher a donné l’allusion finale.
Dans ma deuxième classe j'ai utilisé
Runnable run = new Runnable(){
public void run(){
contactsActivity.update();
}
};
contactsActivity.runOnUiThread(run);
accéder à la mise à jour () de mon activité. Cette mise à jour comprend
myAdapter.notifyDataSetChanged();
indiquer à l'adaptateur d'actualiser la vue . a bien fonctionné dans la mesure du possible.
Si vous utilisez SimpleCursorAdapter essayez d'appeler requery () sur l'objet Cursor.
si vous n'êtes toujours pas satisfait de ListView Refreshment, vous pouvez consulter cet extrait, destiné au chargement du listView à partir de la base de données. En réalité, vous devez simplement recharger le ListView après avoir effectué une opération CRUD meilleure façon de coder, mais cela actualisera le ListView à votre guise.
Cela fonctionne pour moi .... si vous trouvez une meilleure solution, s'il vous plaît partager ...
....... ...... effectuez vos opérations CRUD .......................... DBAdapter. open (); DBAdapter.insert_into_SingleList (); // Apportez ce DB_results et ajoutez-le à la liste en tant que son contenu .... ls2.setAdapter (nouvel ArrayAdapter (DynTABSample.this, Android.R.layout.simple_list_item_1, DBAdapter.DB_ListView)); DBAdapter.close ();
Les solutions proposées par les utilisateurs de ce post fonctionnent ou non principalement en fonction de la version Android de votre appareil. Par exemple, pour utiliser la méthode AddAll, vous devez mettre Android: minSdkVersion = "10" dans votre appareil Android.
Pour résoudre cette question pour tous les périphériques, j'ai créé ma propre méthode dans mon adaptateur et je l'utilise à l'intérieur de la méthode d'ajout et de suppression héritée de ArrayAdapter qui met à jour vos données sans problèmes.
Mon code: En utilisant ma propre classe de données RaceResult, vous utilisez votre propre modèle de données.
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);
}
}
RésultatsGp.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!!!
Il suffit d'utiliser myArrayList.remove(position);
dans un auditeur:
myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override public void onItemClick(AdapterView<?> parent, Android.view.View view, int position, long id) {
myArrayList.remove(position);
myArrayAdapter.notifyDataSetChanged();
}
});
Si vous souhaitez conserver votre position de défilement lors de l'actualisation, procédez comme suit:
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();
}
Pour plus d'informations, consultez Android ListView: Conservez votre position de défilement lorsque vous actualisez .
Si vous souhaitez mettre à jour la vue liste de l'interface utilisateur à partir d'un service, réglez l'adaptateur dans votre activité principale et procédez comme suit:
@Override
public void onDestroy() {
if (MainActivity.isInFront == true) {
if (MainActivity.adapter != null) {
MainActivity.adapter.notifyDataSetChanged();
}
MainActivity.listView.setAdapter(MainActivity.adapter);
}
}
J'ai eu un ArrayList que je voulais afficher dans une liste. ArrayList contenait des éléments de mysql. J'ai remplacé la méthode onRefresh et dans cette méthode, j'ai utilisé tablelayout.removeAllViews (); et ensuite répète le processus pour récupérer les données de la base de données . Mais avant cela, assurez-vous d'effacer votre ArrayList ou toute autre structure de données, sinon de nouvelles données seront ajoutées à l'ancienne.
Pour moi, après avoir modifié les informations de la base de données SQL, rien ne pouvait actualiser la vue liste (pour être une vue liste développable spécifique). Par conséquent, si notifyDataSetChanged () ne résout pas le problème, vous pouvez essayer d’effacer votre liste d’abord et de la rajouter après cet appel notifyDataSetChanged (). Par exemple
private List<List<SomeNewArray>> arrayList;
List<SomeNewArray> array1= getArrayList(...);
List<SomeNewArray> array2= getArrayList(...);
arrayList.clear();
arrayList.add(array1);
arrayList.add(array2);
notifyDataSetChanged();
J'espère que cela a du sens pour vous.
Si je parle de mon scénario ici, aucune des réponses ci-dessus ne fonctionnera, car j’ai eu une activité montrant la liste des valeurs de la base de données avec un bouton de suppression. Lorsque vous appuyez sur un bouton de suppression, je souhaite supprimer cet élément de la liste.
La chose intéressante était que je n'utilisais pas la vue recycleur mais une vue liste simple et cette vue liste initialisée dans la classe d'adaptateur. Ainsi, l'appel de notifyDataSetChanged()
ne fera rien dans la classe d'adaptateur et même dans la classe d'activité où l'objet d'adaptateur est initialisé car la méthode delete était dans la classe d'adaptateur.
La solution consistait donc à supprimer l'objet de l'adaptateur dans la méthode getView
de la classe d'adaptateur (pour supprimer uniquement cet objet spécifique mais si vous souhaitez tout supprimer, appelez clear()
).
À vous de vous faire une idée, à quoi ressemblait mon code,
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
}
});
//....
Remarque: ici, les méthodes remove()
et getItem()
sont héritées de la classe Adapter.
remove()
- pour supprimer l'élément spécifique sur lequel l'utilisateur a cliquégetItem(position)
- est d’obtenir l’élément (ici, c’est mon objet Word que j’ai ajouté à la liste) à partir de la position cliquée.Voici comment je règle l'adaptateur sur listview dans la classe d'activité,
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);
Si vous utilisez les repères Android et que vous utilisez ContentProviders
pour obtenir les données de Database
et que vous les affichez dans ListView
à l’aide des CursorLoader
et CursorAdapters
, toutes les modifications apportées aux données associées seront automatiquement répercutées dans le ListView.
Votre getContext().getContentResolver().notifyChange(uri, null);
sur le curseur dans ContentProvider
suffira à refléter les modifications. Pas besoin de travail supplémentaire.
Mais lorsque vous n'utilisez pas tous ces éléments, vous devez indiquer à l'adaptateur la modification du jeu de données. De plus, vous devez re-peupler/recharger votre jeu de données (liste par exemple), puis appeler notifyDataSetChanged()
sur l'adaptateur.
notifyDataSetChanged()
wont fonctionne s'il n'y a pas de changements dans le jeu de données . Voici le commentaire au-dessus de la méthode dans docs-
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
J'étais le même quand, dans un fragment, je voulais remplir un ListView (dans un seul TextView) avec l'adresse mac des périphériques BLE analysés au fil du temps.
Ce que j'ai fait était ceci:
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();
}
}
Ensuite, ListView a commencé à peupler de manière dynamique l'adresse MAC des périphériques trouvés.
tout en utilisant SimpleCursorAdapter peut appeler changeCursor (newCursor) sur l’adaptateur.
NotifyDataSetChanged () ne fonctionnait pas avec la mise à jour de SimpleAdapter. J'ai donc d'abord essayé de supprimer toutes les vues associées à la présentation parent à l'aide de removeAllViews (), puis d'ajouter 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);
une autre option est la méthode onWindowFocusChanged
, mais elle est sensible et nécessite un codage supplémentaire pour ceux qui sont intéressés.
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
}
Je n'ai pu obtenir notifyDataSetChanged qu'en obtenant de nouvelles données d'adaptateur, puis en réinitialisant l'adaptateur pour la vue liste, puis en effectuant l'appel comme suit:
expandableAdapter = baseFragmentParent.setupEXLVAdapter();
baseFragmentParent.setAdapter(expandableAdapter);
expandableAdapter.notifyDataSetChanged();
Je pense que cela dépend de ce que vous entendez par rafraîchissement. Voulez-vous dire que l'affichage de l'interface graphique doit être actualisé ou que les vues enfant doivent être actualisées afin que vous puissiez appeler par programmation getChildAt (int) et obtenir la vue correspondant à ce qui se trouve dans l'adaptateur.
Si vous souhaitez que l'affichage de l'interface graphique soit actualisé, appelez notifyDataSetChanged () sur l'adaptateur. L'interface graphique sera actualisée lors de la prochaine redessin.
Si vous voulez pouvoir appeler getChildAt (int) et obtenir une vue reflétant ce qui se trouve dans l'adaptateur, appelez layoutChildren (). Cela entraînera la reconstruction de la vue enfant à partir des données de l'adaptateur.
Après avoir supprimé des données de la vue liste, vous devez appeler refreshDrawableState()
. Voici l'exemple:
final DatabaseHelper db = new DatabaseHelper (ActivityName.this);
db.open();
db.deleteContact(arg3);
mListView.refreshDrawableState();
db.close();
et la méthode deleteContact
dans la classe DatabaseHelper
ressemblera un peu à
public boolean deleteContact(long rowId) {
return db.delete(TABLE_NAME, BaseColumns._ID + "=" + rowId, null) > 0;
}