Jak odświeżyć system Android ListView
po dodaniu / usunięciu danych dynamicznych?
Jak odświeżyć system Android ListView
po dodaniu / usunięciu danych dynamicznych?
Odpowiedzi:
Zadzwoń notifyDataSetChanged()
na Adapter
obiekcie Po zmodyfikowaniu danych w tym adapterem.
Niektóre dodatkowe informacje na temat tego, jak / kiedy zadzwonić, notifyDataSetChanged()
można obejrzeć w tym filmie We / Wy Google .
Możesz także użyć tego:
myListView.invalidateViews();
invalidateViews()
w zestawach kodów źródłowych, mDataChanged = true;
więc nie jestem pewien, czy to spowoduje lepszą wydajność w porównaniu donotifyDataSetChanged()
invalidate()
, invalidateViews()
, requestLayout()
... odpowiedź na to pytanie.Właściwą czynnością (i na szczęście oznaczoną również jako prawidłową odpowiedź) jest zadzwonienie do notifyDataSetChanged()
adaptera .
Jeśli wywołanie notifyDataSetChanged()
nie działa, wszystkie metody układu również nie pomogą. Uwierz mi, że ListView
został odpowiednio zaktualizowany. Jeśli nie znajdziesz różnicy, musisz sprawdzić, skąd pochodzą dane w karcie.
Jeśli jest to tylko kolekcja, którą przechowujesz w pamięci, sprawdź, czy faktycznie ją usunąłeś lub dodałeś do kolekcji przed wywołaniem notifyDataSetChanged()
.
Jeśli pracujesz z bazą danych lub zapleczem usługi, musisz wywołać metodę, aby ponownie pobrać informacje (lub manipulować danymi w pamięci) przed wywołaniem notifyDataSetChanged()
.
Chodzi o to, że notifyDataSetChanged
działa to tylko wtedy, gdy zestaw danych się zmienił. To jest miejsce, w którym należy sprawdzić, czy nie zauważysz zmian. W razie potrzeby debuguj.
Odkryłem, że praca z adapterem, który pozwala zarządzać kolekcją, tak jak BaseAdapter działa lepiej. Niektóre adaptery, takie jak ArrayAdapter, już zarządzają własną kolekcją, co utrudnia dostęp do odpowiedniej kolekcji w celu aktualizacji. W większości przypadków jest to po prostu niepotrzebna dodatkowa warstwa trudności.
Prawdą jest, że należy to wywołać z wątku interfejsu użytkownika. Inne odpowiedzi zawierają przykłady, jak to osiągnąć. Jest to jednak wymagane tylko w przypadku pracy z tymi informacjami spoza wątku interfejsu użytkownika. To jest z usługi lub wątku innego niż interfejs użytkownika. W prostych przypadkach będziesz aktualizować swoje dane za pomocą kliknięcia przycisku lub innej aktywności / fragmentu. Więc nadal w wątku interfejsu użytkownika. Nie trzeba zawsze otwierać tego runOnUiTrhead.
Można znaleźć na https://github.com/hanscappelle/so-2250770.git . Wystarczy sklonować i otworzyć projekt w Android Studio (stopień). Ten projekt ma MainAciency budujący ListView
wszystkie losowe dane. Tę listę można odświeżyć za pomocą menu akcji.
Implementacja adaptera utworzona dla tego przykładu ModelObject udostępnia kolekcję danych
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
}
Kod z 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();
}
}
Kolejny fajny post o sile listViews znajduje się tutaj: http://www.vogella.com/articles/AndroidListView/article.html
adapter.clear()
, a adapter.addAll(Array<T>)
przed dzwonieniemnotifyDataSetChanged()
Dzwoń w dowolnym momencie:
runOnUiThread(run);
OnCreate()
, ustawiasz wątek uruchamialny:
run = new Runnable() {
public void run() {
//reload content
arraylist.clear();
arraylist.addAll(db.readAll());
adapter.notifyDataSetChanged();
listview.invalidateViews();
listview.refreshDrawableState();
}
};
runOnUiThread()
funkcji.
run = new Runnable()
i po prostu nadałem mojej funkcji public void refreshUIThread()
metodę, która modeluje twoją run()
metodę (która też działa).
Mam problemy z dynamicznym odświeżaniem widoku listy.
Wywołaj powiadomienieDataSetChanged () na karcie.
Niektóre dodatkowe informacje na temat tego, jak / kiedy wywołać powiadomienieDataSetChanged () można obejrzeć w tym filmie Google I / O.
powiadomienieDataSetChanged () nie działało poprawnie w moim przypadku [wywołałem powiadomienieDataSetChanged z innej klasy]. Właśnie w przypadku, gdy edytowałem ListView w działającym działaniu (wątku). Ten film dzięki Christopherowi dał ostatnią wskazówkę.
W mojej drugiej klasie korzystałem
Runnable run = new Runnable(){
public void run(){
contactsActivity.update();
}
};
contactsActivity.runOnUiThread(run);
aby uzyskać dostęp do aktualizacji () z mojej Aktywności. Ta aktualizacja obejmuje
myAdapter.notifyDataSetChanged();
powiedzieć adapterowi, aby odświeżył widok. Działało dobrze, o ile mogę powiedzieć.
Jeśli używasz SimpleCursorAdapter, spróbuj wywołać requery () na obiekcie Cursor.
jeśli nadal nie jesteś zadowolony z ListView Refreshment, możesz spojrzeć na ten fragment kodu, służy on do wczytywania listView z bazy danych. Właściwie to po prostu przeładuj ListView po wykonaniu jakiejkolwiek operacji CRUD. Nie jest to najlepszy sposób na kod, ale odświeży ListView, jak chcesz ..
Działa dla mnie .... jeśli znajdziesz lepsze rozwiązanie, udostępnij ...
....... ...... wykonaj swoje operacje CRUD .. ...... ..... DBAdapter.open (); DBAdapter.insert_into_SingleList (); // Przynieś wyniki DB_result i dodaj go do listy jako jego zawartość .... ls2.setAdapter (nowy ArrayAdapter (DynTABSample.this, android.R.layout.simple_list_item_1, DBAdapter.DB_ListView)); DBAdapter.close ();
Rozwiązania zaproponowane przez ludzi w tym poście działają lub nie, głównie w zależności od wersji Androida urządzenia. Na przykład, aby użyć metody AddAll, musisz umieścić Androida: minSdkVersion = "10" na urządzeniu z Androidem.
Aby rozwiązać te pytania dla wszystkich urządzeń, utworzyłem własną metodę w adapterze i używam wewnątrz metody dodawania i usuwania dziedziczenia z ArrayAdapter, które aktualizują dane bez problemów.
Mój kod: Używając własnej klasy danych RaceResult, korzystasz z własnego modelu danych.
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!!!
Jeśli chcesz zachować pozycję przewijania podczas odświeżania i możesz to zrobić:
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();
}
Aby uzyskać szczegółowe informacje, zobacz Android ListView: Utrzymaj pozycję przewijania podczas odświeżania .
Wystarczy użyć myArrayList.remove(position);
wewnątrz detektora:
myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override public void onItemClick(AdapterView<?> parent, android.view.View view, int position, long id) {
myArrayList.remove(position);
myArrayAdapter.notifyDataSetChanged();
}
});
Musisz użyć jednego obiektu z tej listy, do którego danych chcesz nadmuchać ListView
. Jeśli odwołanie jest zmieniane, to notifyDataSetChanged()
nie działa. Za każdym razem, gdy usuwasz elementy z widoku listy, usuwaj je również z używanej listy, niezależnie od tego, czy jest to ArrayList <>, czy coś innego niż Wywołaj
notifyDataSetChanged()
obiekt klasy adaptera.
Zobacz więc, jak zarządzałem w mojej przejściówce, patrz poniżej
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();
}
}
Po usunięciu danych z widoku listy musisz zadzwonić refreshDrawableState()
. Oto przykład:
final DatabaseHelper db = new DatabaseHelper (ActivityName.this);
db.open();
db.deleteContact(arg3);
mListView.refreshDrawableState();
db.close();
i deleteContact
metoda w DatabaseHelper
klasie będzie wyglądać nieco
public boolean deleteContact(long rowId) {
return db.delete(TABLE_NAME, BaseColumns._ID + "=" + rowId, null) > 0;
}
Nie byłem w stanie uzyskać powiadomieniaDataSetChanged () do pracy nad aktualizacją mojego SimpleAdapter, więc zamiast tego próbowałem najpierw usunąć wszystkie widoki, które zostały dołączone do układu nadrzędnego za pomocą removeAllViews (), a następnie dodać ListView i działało, pozwalając mi zaktualizować Interfejs użytkownika:
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);
Dla mnie po zmianie informacji w bazie danych SQL nic nie może odświeżyć widoku listy (a konkretnie rozwijanego widoku listy), więc jeśli powiadomienieataDataSetChanged () nie pomoże, możesz spróbować wyczyścić listę i dodać ją ponownie po tym wywołaniu powiadomićDataSetChanged (). Na przykład
private List<List<SomeNewArray>> arrayList;
List<SomeNewArray> array1= getArrayList(...);
List<SomeNewArray> array2= getArrayList(...);
arrayList.clear();
arrayList.add(array1);
arrayList.add(array2);
notifyDataSetChanged();
Mam nadzieję, że ma to dla ciebie sens.
podczas korzystania z SimpleCursorAdapter może wywoływać changeCursor (newCursor) na adapterze.
Byłem taki sam, gdy fragmentem chciałem wypełnić ListView (w pojedynczym TextView) adresem MAC urządzeń BLE skanowanych przez pewien czas.
Zrobiłem to:
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();
}
}
Następnie ListView zaczął dynamicznie wypełniać adres mac znalezionych urządzeń.
Myślę, że to zależy od tego, co masz na myśli przez odświeżenie. Czy masz na myśli, że ekran GUI powinien zostać odświeżony, czy masz na myśli, że widoki potomne powinny zostać odświeżone, abyś mógł programowo wywołać getChildAt (int) i uzyskać widok odpowiadający temu, co jest w adapterze.
Jeśli chcesz odświeżyć ekran GUI, wywołaj powiadomienieataDataSetChanged () na adapterze. GUI zostanie odświeżony przy następnym przerysowaniu.
Jeśli chcesz mieć możliwość wywołania getChildAt (int) i uzyskania widoku odzwierciedlającego to, co jest w adapterze, to wywołaj layoutChildren (). Spowoduje to zrekonstruowanie widoku potomnego na podstawie danych adaptera.
Miałem ArrayList, który chciałem wyświetlić w widoku listy. ArrayList zawierał elementy z mysql. Przesłoniłem metodę onRefresh iw tej metodzie użyłem tablelayout.removeAllViews (); a następnie powtórzył proces ponownego pobierania danych z bazy danych. Ale przed tym upewnij się, że wyczyściłeś ArrayList lub jakąkolwiek strukturę danych, albo nowe dane zostaną dołączone do starej.
Jeśli chcesz zaktualizować widok listy interfejsu użytkownika z usługi, ustaw statyczny adapter w działaniu głównym i wykonaj następujące czynności:
@Override
public void onDestroy() {
if (MainActivity.isInFront == true) {
if (MainActivity.adapter != null) {
MainActivity.adapter.notifyDataSetChanged();
}
MainActivity.listView.setAdapter(MainActivity.adapter);
}
}
Jeśli korzystasz z linii prowadzących Androida i używasz ContentProviders
do pobierania danych Database
i wyświetlasz je za ListView
pomocą CursorLoader
iCursorAdapters
, wtedy wszystkie zmiany w powiązanych danych zostaną automatycznie odzwierciedlone w ListView.
Twój getContext().getContentResolver().notifyChange(uri, null);
kursor wContentProvider
będzie wystarczający, aby odzwierciedlić zmiany. Nie musisz wykonywać dodatkowej pracy.
Ale jeśli nie używasz ich wszystkich, musisz poinformować adapter o zmianie zestawu danych. Musisz również ponownie wypełnić / ponownie załadować zestaw danych (powiedzmy listę), a następnie musisz zadzwonićnotifyDataSetChanged()
adapter.
notifyDataSetChanged()
nie będzie działać, jeśli nie ma zmian w zestawie danych. Oto komentarz nad metodą w dokumentach
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
Byłem w stanie uzyskać powiadomienieDataSetChanged tylko poprzez pobranie nowych danych adaptera, a następnie zresetowanie adaptera do widoku listy, a następnie wykonanie połączenia w następujący sposób:
expandableAdapter = baseFragmentParent.setupEXLVAdapter();
baseFragmentParent.setAdapter(expandableAdapter);
expandableAdapter.notifyDataSetChanged();
drugą opcją jest onWindowFocusChanged
metoda, ale pewna, że jest wrażliwa i wymaga dodatkowego kodowania dla kogo jest zainteresowana
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
}
Rozważ, że przekazałeś listę do adaptera.
Posługiwać się:
list.getAdapter().notifyDataSetChanged()
zaktualizować listę.
Jeśli mówiłem tutaj o moim scenariuszu, żadne z powyższych odpowiedzi nie zadziałałoby, ponieważ miałem aktywność, która pokazuje listę wartości db wraz z przyciskiem usuwania, a kiedy przycisk usuwania jest naciśnięty, chciałem usunąć ten element z listy.
Fajne było to, że nie użyłem widoku programu do recyklingu, ale prosty widok listy i ten widok listy zainicjowano w klasie adaptera. Więc dzwoniąc donotifyDataSetChanged()
nie spowoduje nic wewnątrz klasy adaptera, a nawet w klasie aktywności, w której inicjowany jest obiekt adaptera, ponieważ metoda usuwania była w klasie adaptera.
Rozwiązaniem było więc usunięcie obiektu z adaptera w getView
metodzie klasy adaptera (aby usunąć tylko ten konkretny obiekt, ale jeśli chcesz usunąć wszystko, wywołajclear()
).
Aby dowiedzieć się, jak wyglądał mój kod,
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
}
});
//....
Uwaga: tutaj remove()
i getItem()
metody są dziedziczone z klasy Adapter.
remove()
- aby usunąć konkretny kliknięty elementgetItem(position)
- ma pobrać element (tutaj, to mój obiekt Word, który dodałem do listy) z klikniętej pozycji.W ten sposób ustawiam adapter na widok listy w klasie aktywności,
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);
Najłatwiej jest po prostu zrobić nowego Adapera i upuścić starego:
myListView.setAdapter(new MyListAdapter(...));