Mam, ListView
który wyświetla wiadomości. Zawierają obraz, tytuł i tekst. Obraz jest ładowany w osobnym wątku (z kolejką i wszystkimi innymi), a po pobraniu obrazu wywołuję teraz notifyDataSetChanged()
adapter listy, aby zaktualizować obraz. To działa, ale getView()
jest wywoływane zbyt często, ponieważ notifyDataSetChanged()
wymaga getView()
wszystkich widocznych elementów. Chcę zaktualizować tylko jedną pozycję na liście. Jak bym to zrobił?
Problemy, które mam z moim obecnym podejściem, to:
- Przewijanie jest powolne
- Mam animację zanikania obrazu, która ma miejsce za każdym razem, gdy ładowany jest pojedynczy nowy obraz z listy.
To pytanie zostało zadane na Google I / O 2010, możesz je obejrzeć tutaj:
Świat ListView, godzina 52:30
Zasadniczo co Romain Guy wyjaśnia to zadzwonić
getChildAt(int)
naListView
, aby uzyskać widok i (chyba) wezwaniegetFirstVisiblePosition()
, aby dowiedzieć się korelację pomiędzy pozycją i indeksu.Romain jako przykład wskazuje również projekt o nazwie Półki , myślę, że może mieć na myśli metodę
ShelvesActivity.updateBookCovers()
, ale nie mogę znaleźć wezwaniagetFirstVisiblePosition()
.NADCHODZĄCE AKTUALIZACJE:
RecyclerView naprawi ten problem w najbliższej przyszłości. Jak wskazano na http://www.grokkingandroid.com/first-glance-androids-recyclerview/ , będziesz mógł wywołać metody, aby dokładnie określić zmianę, na przykład:
void notifyItemInserted(int position) void notifyItemRemoved(int position) void notifyItemChanged(int position)
Ponadto każdy będzie chciał skorzystać z nowych widoków opartych na RecyclerView, ponieważ zostaną nagrodzeni ładnie wyglądającymi animacjami! Przyszłość wygląda niesamowicie! :-)
źródło
Oto jak to zrobiłem:
Twoje pozycje (wiersze) muszą mieć unikalne identyfikatory, aby można było je później zaktualizować. Ustaw znacznik każdego widoku, gdy lista pobiera widok z adaptera. (Możesz również użyć tagu klucza, jeśli tag domyślny jest używany w innym miejscu)
@Override public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); view.setTag(getItemId(position)); return view; }
Do aktualizacji sprawdź każdy element listy, czy widok o podanym id jest widoczny, więc wykonujemy aktualizację.
private void update(long id) { int c = list.getChildCount(); for (int i = 0; i < c; i++) { View view = list.getChildAt(i); if ((Long)view.getTag() == id) { // update view } } }
W rzeczywistości jest to łatwiejsze niż inne metody i lepsze, gdy masz do czynienia z identyfikatorami, a nie pozycjami! Musisz także zadzwonić do aktualizacji dla przedmiotów, które stają się widoczne.
źródło
pobierz najpierw klasę modelu jako globalną, jak ten obiekt klasy modelu
SampleModel golbalmodel=new SchedulerModel();
i zainicjuj go na global
pobierz bieżący wiersz widoku przez model, inicjując go w modelu globalnym
SampleModel data = (SchedulerModel) sampleList.get(position); golbalmodel=data;
ustaw zmienioną wartość na metodę obiektu modelu globalnego, która ma zostać ustawiona, i dodaj informację notifyDataSetChanged dla mnie
golbalmodel.setStartandenddate(changedate); notifyDataSetChanged();
Oto powiązane pytanie z dobrymi odpowiedziami.
źródło
Odpowiedzi są jasne i poprawne, dodam
CursorAdapter
tutaj pomysł na przypadek.Jeśli korzystasz z podklasy
CursorAdapter
(lubResourceCursorAdapter
, lubSimpleCursorAdapter
), możesz zaimplementowaćViewBinder
lub przesłonićbindView()
inewView()
metody, które nie otrzymują bieżącego indeksu elementu listy w argumentach. Dlatego, gdy przychodzą jakieś dane i chcesz zaktualizować odpowiednie widoczne elementy listy, skąd znasz ich indeksy?Moje obejście polegało na:
newView()
notifyDatasetChanged()
i odświeżanie ich wszystkichZe względu na ponowne wyświetlanie widoku liczba odniesień widoku, które będę musiał przechowywać i iterować, będzie mniej więcej równa liczbie elementów listy widocznych na ekranie.
źródło
int wantedPosition = 25; // Whatever position you're looking for int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0 int wantedChild = wantedPosition - firstPosition; if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) { Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen."); return; } View wantedView = linearLayoutManager.getChildAt(wantedChild); mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over); mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup); mlayoutOver.setVisibility(View.INVISIBLE); mlayoutPopup.setVisibility(View.VISIBLE);
W przypadku RecycleView użyj tego kodu
źródło
Użyłem kodu, który dostarczył Erikowi, działa świetnie, ale mam złożony niestandardowy adapter do mojego widoku listy i miałem do czynienia z dwukrotną implementacją kodu aktualizującego interfejs użytkownika. Próbowałem uzyskać nowy widok z metody getView moich adapterów (lista arraylista przechowująca dane widoku listy została już zaktualizowana / zmieniona):
View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition()); if(cell!=null){ cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent) cell.startAnimation(animationLeftIn()); }
Działa dobrze, ale nie wiem, czy to dobra praktyka. Więc nie muszę implementować kodu, który aktualizuje element listy dwa razy.
źródło
dokładnie tego użyłem
private void updateSetTopState(int index) { View v = listview.getChildAt(index - listview.getFirstVisiblePosition()+listview.getHeaderViewsCount()); if(v == null) return; TextView aa = (TextView) v.findViewById(R.id.aa); aa.setVisibility(View.VISIBLE); }
źródło
Wymyśliłem inne rozwiązanie, jak void metody RecyclyerView
notifyItemChanged(int position)
, stworzyłem klasę CustomBaseAdapter tak:public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter { private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable(); public boolean hasStableIds() { return false; } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); } public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); } public void notifyItemChanged(int position) { mDataSetObservable.notifyItemChanged(position); } public void notifyDataSetInvalidated() { mDataSetObservable.notifyInvalidated(); } public boolean areAllItemsEnabled() { return true; } public boolean isEnabled(int position) { return true; } public View getDropDownView(int position, View convertView, ViewGroup parent) { return getView(position, convertView, parent); } public int getItemViewType(int position) { return 0; } public int getViewTypeCount() { return 1; } public boolean isEmpty() { return getCount() == 0; } { } }
Nie zapomnij utworzyć klasy CustomDataSetObservable również dla
mDataSetObservable
zmiennej w CustomAdapterClass , na przykład:public class CustomDataSetObservable extends Observable<DataSetObserver> { public void notifyChanged() { synchronized(mObservers) { // since onChanged() is implemented by the app, it could do anything, including // removing itself from {@link mObservers} - and that could cause problems if // an iterator is used on the ArrayList {@link mObservers}. // to avoid such problems, just march thru the list in the reverse order. for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } } public void notifyInvalidated() { synchronized (mObservers) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onInvalidated(); } } } public void notifyItemChanged(int position) { synchronized(mObservers) { // since onChanged() is implemented by the app, it could do anything, including // removing itself from {@link mObservers} - and that could cause problems if // an iterator is used on the ArrayList {@link mObservers}. // to avoid such problems, just march thru the list in the reverse order. mObservers.get(position).onChanged(); } } }
w klasie CustomBaseAdapter znajduje się metoda
notifyItemChanged(int position)
, którą możesz wywołać, gdy chcesz zaktualizować wiersz w dowolnym miejscu (po kliknięciu przycisku lub w dowolnym miejscu, w którym chcesz wywołać tę metodę). I voila!, Twój pojedynczy wiersz zostanie natychmiast zaktualizowany.źródło
Moje rozwiązanie: jeśli jest poprawne *, zaktualizuj dane i widoczne elementy bez ponownego rysowania całej listy. Else notifyDataSetChanged.
Prawidłowo - stary rozmiar danych == nowy rozmiar danych oraz stare identyfikatory danych i ich kolejność == nowe identyfikatory i kolejność danych
W jaki sposób:
/** * A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping * is one-to-one and on. * */ private static class UniqueValueSparseArray extends SparseArray<View> { private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>(); @Override public void put(int key, View value) { final Integer previousKey = m_valueToKey.put(value,key); if(null != previousKey) { remove(previousKey);//re-mapping } super.put(key, value); } } @Override public void setData(final List<? extends DBObject> data) { // TODO Implement 'smarter' logic, for replacing just part of the data? if (data == m_data) return; List<? extends DBObject> oldData = m_data; m_data = null == data ? Collections.EMPTY_LIST : data; if (!updateExistingViews(oldData, data)) notifyDataSetChanged(); else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged"); } /** * See if we can update the data within existing layout, without re-drawing the list. * @param oldData * @param newData * @return */ private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) { /** * Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible * items. */ final int oldDataSize = oldData.size(); if (oldDataSize != newData.size()) return false; DBObject newObj; int nVisibleViews = m_visibleViews.size(); if(nVisibleViews == 0) return false; for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) { newObj = newData.get(position); if (oldData.get(position).getId() != newObj.getId()) return false; // iterate over visible objects and see if this ID is there. final View view = m_visibleViews.get(position); if (null != view) { // this position has a visible view, let's update it! bindView(position, view, false); nVisibleViews--; } } return true; }
i oczywiście:
@Override public View getView(final int position, final View convertView, final ViewGroup parent) { final View result = createViewFromResource(position, convertView, parent); m_visibleViews.put(position, result); return result; }
Zignoruj ostatni parametr bindView (używam go do określenia, czy muszę ponownie przetwarzać mapy bitowe dla ImageDrawable).
Jak wspomniano powyżej, całkowita liczba „widocznych” widoków to mniej więcej liczba, która mieści się na ekranie (pomijając zmiany orientacji itp.), Więc nie ma dużej pamięci.
źródło
Oprócz tego rozwiązania ( https://stackoverflow.com/a/3727813/5218712 ) chcę tylko dodać, że powinno działać tylko wtedy, gdy
listView.getChildCount() == yourDataList.size();
w ListView może być dodatkowy widok.Przykład wypełniania elementów podrzędnych:
źródło