2010-09-16 10 views
114

Ich habe eine ListView, die Nachrichten anzeigt. Sie enthalten ein Bild, einen Titel und etwas Text. Das Bild wird in einen separaten Thread geladen (mit einer Warteschlange und alle) und wenn das Bild heruntergeladen wird, rufe ich jetzt notifyDataSetChanged() auf dem Listenadapter auf, um das Bild zu aktualisieren. Das funktioniert, aber getView() wird zu häufig aufgerufen, seit notifyDataSetChanged() ruft getView() für alle sichtbaren Elemente. Ich möchte nur das einzelne Element in der Liste aktualisieren. Wie würde ich das tun?Wie kann ich eine einzelne Zeile in einem ListView aktualisieren?

Probleme ich mit meiner aktuellen Ansatz haben, sind:

  1. Scrolling langsam ist
  2. ich ein Fade-in-Animation auf dem Bild haben, die geladen wird, jedes Mal, ein einziges neues Bild in der Liste geschieht.

Antwort

176

Ich fand die Antwort, dank Ihrer Informationen Michelle. Sie können tatsächlich die richtige Ansicht mit View#getChildAt(int index) bekommen. Der Haken ist, dass es ab dem ersten sichtbaren Gegenstand zu zählen beginnt. Tatsächlich können Sie nur die sichtbaren Gegenstände erhalten. Sie lösen dies mit ListView#getFirstVisiblePosition().

Beispiel:

private void updateView(int index){ 
    View v = yourListView.getChildAt(index - 
     yourListView.getFirstVisiblePosition()); 

    if(v == null) 
     return; 

    TextView someText = (TextView) v.findViewById(R.id.sometextview); 
    someText.setText("Hi! I updated you manually!"); 
} 
+2

Hinweis zu Selbst: mImgView.setImageURI() wird das Listenelement aber nur einmal aktualisieren; Daher ist es besser, stattdessen mstvwAdapter.notifyDataSetInvalidated() zu verwenden. – kellogs

+0

Diese Lösung funktioniert. Dies kann jedoch nur mit yourListView.getChildAt (index); und ändere die Ansicht. Beide Lösungen funktionieren !! – MobiDev

+1

@erik können Sie mir dabei helfen http://stackoverflow.com/questions/18312539/update-the-value-of-item-in-the-listview-android?noredirect=1#comment26873838_18312539 – Developer

65

Diese Frage auf der Google I/O 2010 gefragt wurde, können Sie es hier sehen kann:

The world of ListView, time 52:30

Grundsätzlich erklärt, was Romain Guy ist getChildAt(int) auf der ListView rufen Sie die Ansicht zu erhalten und (denke ich) rufen Sie getFirstVisiblePosition() an, um die Korrelation zwischen Position und Index herauszufinden.

Romain zeigt auch auf das Projekt namens Shelves als ein Beispiel, ich denke, er könnte die Methode ShelvesActivity.updateBookCovers() bedeuten, aber ich kann den Anruf von nicht finden.

FANTASTISCHEN UPDATES COMING:

Die RecyclerView wird dies in naher Zukunft beheben. Wie auf http://www.grokkingandroid.com/first-glance-androids-recyclerview/ darauf hingewiesen, können Sie Methoden aufrufen, um genau die Änderung angeben, wie zB:

void notifyItemInserted(int position) 
void notifyItemRemoved(int position) 
void notifyItemChanged(int position) 

Auch jeder wird wollen die neuen Ansichten auf RecyclerView Basis verwenden, weil sie mit belohnt werden schön aussehende Animationen! Die Zukunft sieht fantastisch aus! :-)

+2

posted ich die Lösung. Danke für deine Information! – Erik

+3

Gute Sache! :) Vielleicht kannst du auch hier einen Null-Check hinzufügen - v kann null sein, wenn die Ansicht momentan nicht verfügbar ist. Und natürlich sind diese Daten verschwunden, wenn der Benutzer die ListView scrollt, also sollte man auch die Daten im Adapter aktualisieren (vielleicht ohne notifyDataSetChanged() aufzurufen). Im Allgemeinen denke ich, dass es eine gute Idee wäre, all diese Logik innerhalb des Adapters zu behalten, d. H. Den ListView-Bezug darauf zu übergeben. – mreichelt

+0

Dies funktionierte für mich, außer - wenn die Ansicht den Bildschirm verließ, wird sie beim erneuten Eingeben der Änderung zurückgesetzt. Ich schätze, weil es in der getview immer noch die ursprünglichen Informationen erhält. Wie komme ich hier zurecht? – gloscherrybomb

2

Die Antworten sind klar und richtig, ich werde eine Idee für CursorAdapter Fall hier zufügen.

Wenn youre Subklassen CursorAdapter (oder ResourceCursorAdapter oder SimpleCursorAdapter), erhalten Sie entweder ViewBinder zu implementieren oder bindView() und newView() Methoden außer Kraft setzen, diese nicht erhalten aktuelle Listenelement Index in Argumente. Wenn also einige Daten eintreffen und Sie relevante sichtbare Listenelemente aktualisieren möchten, woher kennen Sie dann ihre Indizes?halten

  • eine Liste aller erstellten Liste Artikel Ansichten, fügen Sie Artikel zu dieser Liste von newView()
  • wenn Daten ankommen, sie durchlaufen und sehen, welche aktualisiert werden muss - besser: zu

    war meine Abhilfe

Aufgrund sehen alle von ihnen notifyDatasetChanged() und erfrischenden Recycling der Anzahl der uuml tun, als ich brauche zu speichern und Iterierte werden die Anzahl der Listenelemente auf dem Bildschirm sichtbar in etwa gleich sein.

1

Ich verwendete den Code, der Erik zur Verfügung stellte, funktioniert großartig, aber ich habe einen komplexen benutzerdefinierten Adapter für meine Listview und ich wurde mit zweimal Implementierung des Codes konfrontiert, der die Benutzeroberfläche aktualisiert. Ich habe versucht, die neue Ansicht von meinem Adapter getView Methode zu erhalten (der Arraylist, die die Listview-Daten enthält hat allready aktualisiert/geändert):

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()); 
} 

Es ist gut zu arbeiten, aber ich weiß nicht, ob dies eine gute ist trainieren. So muss ich den Code, der das Listenelement zweimal aktualisiert, nicht implementieren.

0

Meine Lösung: Wenn es korrekt ist *, aktualisieren Sie die Daten und sichtbaren Elemente, ohne die gesamte Liste neu zu zeichnen. Sonst notifyDataSetChanged.

Correct - olddata Größe == neue Datengröße und alte Daten-IDs und deren Reihenfolge == neue Daten-IDs und Ordnung

Wie:

/** 
* 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; 
} 

und natürlich:

@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; 
} 

Ignoriere den letzten Parameter für bindView (Ich benutze ihn um festzustellen, ob ich Bitmaps für ImageDrawable recyceln muss).

Wie oben erwähnt, ist die Gesamtzahl der "sichtbaren" Ansichten ungefähr die Menge, die auf den Bildschirm passt (Orientierungsänderungen werden ignoriert usw.), also keine großen Speicher.

6

Dies ist, wie ich es tat:

Ihre Artikel (Zeilen) müssen eindeutige IDs haben, damit Sie sie später aktualisieren können. Legen Sie das Tag für jede Ansicht fest, wenn die Liste die Ansicht vom Adapter erhält. (Sie können auch Schlüssel-Tag verwenden, wenn der Standard-Tag irgendwo anders verwendet wird)

@Override 
public View getView(int position, View convertView, ViewGroup parent) 
{ 
    View view = super.getView(position, convertView, parent); 
    view.setTag(getItemId(position)); 
    return view; 
} 

Für die Update-Prüfung jedes Element Liste, wenn eine Ansicht mit angegebener ID ist es sichtbar ist, so führen wir das Update.

Es ist tatsächlich einfacher als andere Methoden und besser, wenn Sie mit Ids nicht Positionen beschäftigen! Außerdem müssen Sie das Update für Elemente aufrufen, die sichtbar werden.

1
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); 

Für RecycleView diesen Code bitte

+0

Ich habe dies auf recycleView implementiert, es funktioniert. Aber wenn Sie die Liste scrollen, wird sie wieder geändert. irgendeine Hilfe? –

+0

Verwenden Sie get und settag –

0

verwenden genau benutzte ich dieses

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

ich eine andere Lösung aus, wie RecyclyerView Methode void notifyItemChanged(int position), erstellen Sie wie folgt CustomBaseAdapter Klasse nur :

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; 
    } { 

    } 
} 

Vergessen Sie nicht, eine CustomDataSetObservable Klasse auch für mDataSetObservable Variable in CustomAdapterClass, wie diese zu erstellen:

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(); 
     } 
    } 
} 

auf Klasse CustomBaseAdapter gibt es ein Verfahren notifyItemChanged(int position), und Sie können diese Methode aufrufen Wenn Sie eine Zeile aktualisieren möchten, wo immer Sie möchten (von der Schaltfläche klicken oder wo Sie diese Methode aufrufen möchten). Und voila !, Ihre einzige Zeile wird sofort aktualisiert ..

2

die Modellklasse erhält zunächst als global wie diese Modellklasse Objekt

SampleModel golbalmodel=new SchedulerModel(); 

und initialisieren es zum globalen

die aktuellen Zeile des bekommen Blick durch das Modell durch die es zum globalen Modell

SampleModel data = (SchedulerModel) sampleList.get(position); 
       golbalmodel=data; 

Initialisierung gesetzt den geänderten Wert zu globalen Modells Objektmethode festgelegt werden und fügen sie den notifyDataSetChanged seine funktioniert bei mir

golbalmodel.setStartandenddate(changedate); 

notifyDataSetChanged(); 

Here is a related question on this with good answers.

Verwandte Themen