Wie kann ich eine einzelne Zeile in einer ListView aktualisieren?

131

Ich habe eine, ListViewdie Nachrichten anzeigt. Sie enthalten ein Bild, einen Titel und Text. Das Bild wird in einen separaten Thread geladen (mit einer Warteschlange und allen) und wenn das Bild heruntergeladen wird, rufe ich jetzt notifyDataSetChanged()den Listenadapter auf, um das Bild zu aktualisieren. Dies funktioniert, aber getView()wird immer zu häufig genannt, da notifyDataSetChanged()Anrufe getView()für alle sichtbaren Elemente. Ich möchte nur das einzelne Element in der Liste aktualisieren. Wie würde ich das machen?

Probleme, die ich mit meinem aktuellen Ansatz habe, sind:

  1. Das Scrollen ist langsam
  2. Ich habe eine Einblendanimation auf dem Bild, die jedes Mal auftritt, wenn ein einzelnes neues Bild in der Liste geladen wird.
Erik
quelle

Antworten:

199

Ich habe die Antwort dank Ihrer Informationen gefunden, Michelle. Sie können in der Tat die richtige Ansicht mit erhalten View#getChildAt(int index). Der Haken ist, dass es ab dem ersten sichtbaren Gegenstand zu zählen beginnt. Tatsächlich können Sie nur die sichtbaren Elemente 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!");
}
Erik
quelle
2
Hinweis für sich selbst: mImgView.setImageURI () aktualisiert das Listenelement nur einmal; Daher ist es besser, stattdessen mLstVwAdapter.notifyDataSetInvalidated () zu verwenden.
Kellogs
Diese Lösung funktioniert. Dies ist jedoch nur mit yourListView.getChildAt (index) möglich. und ändern Sie die Ansicht. Beide Lösungen funktionieren !!
AndroidDev
Was würde passieren, wenn ich getView () auf dem Adapter nur aufrufe, wenn die Position zwischen getFirstVisiblePosition () und getLastVisiblePosition () liegt? würde es genauso funktionieren? Ich denke, es wird die Ansicht wie gewohnt aktualisieren, oder?
Android-Entwickler
In meinem Fall ist die Ansicht manchmal null, da jedes Element asynchron aktualisiert wird. Überprüfen Sie besser, ob die Ansicht! = Null
Khawar
2
Funktioniert super. Es hilft mir, die Aufgabe der Implementierung der Like-Funktion in einer instagram-ähnlichen App abzuschließen. Ich verwendete einen benutzerdefinierten Adapter, eine Broadcast-Schaltfläche in einem Listenelement, startete eine Asynctask, um das Backend mit einem Broadcast-Empfänger auf dem übergeordneten Fragment über Ähnliches zu informieren, und schließlich Eriks Antwort, um die Liste zu aktualisieren.
Josh
71

Diese Frage wurde bei Google I / O 2010 gestellt. Sie können sie hier ansehen:

Die Welt von ListView, Zeit 52:30

Im Grunde , was Romain Guy erklärt ist zu nennen getChildAt(int)auf die ListViewdie Ansicht zu erhalten und (glaube ich) Aufruf getFirstVisiblePosition()die Korrelation zwischen Position und Index , um herauszufinden.

Romain verweist auch auf das Projekt namens Shelves als Beispiel. Ich denke, er meint die Methode ShelvesActivity.updateBookCovers(), aber ich kann den Aufruf von nicht finden getFirstVisiblePosition().

FANTASTISCHE UPDATES KOMMEN:

Das RecyclerView wird dies in naher Zukunft beheben. Wie unter http://www.grokkingandroid.com/first-glance-androids-recyclerview/ ausgeführt , können Sie Methoden aufrufen, um die Änderung genau anzugeben, z.

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

Außerdem wird jeder möchte die neuen Ansichten basierend auf RecyclerView verwenden , da sie mit gut aussehenden Animationen belohnt werden! Die Zukunft sieht großartig aus! :-)

mreichelt
quelle
3
Gute Sache! :) Vielleicht können Sie auch hier eine Nullprüfung hinzufügen - v kann null sein, wenn die Ansicht momentan nicht verfügbar ist. Und natürlich gehen diese Daten verloren, wenn der Benutzer die ListView scrollt, daher sollte man auch die Daten im Adapter aktualisieren (möglicherweise ohne notifyDataSetChanged () aufzurufen). Im Allgemeinen halte ich es für eine gute Idee, all diese Logik im Adapter zu belassen, dh den ListView-Verweis darauf zu übergeben.
mreichelt
Dies hat bei mir funktioniert, außer - wenn die Ansicht den Bildschirm verlassen hat und die Änderung erneut eingegeben wird, wird sie zurückgesetzt. Ich denke, weil in der Getview immer noch die Originalinformationen empfangen werden. Wie komme ich darum herum?
Gloscherrybomb
6

So habe ich es gemacht:

Ihre Elemente (Zeilen) müssen eindeutige IDs haben, damit Sie sie später aktualisieren können. Legen Sie das Tag jeder Ansicht fest, wenn die Liste die Ansicht vom Adapter erhält. (Sie können auch ein Schlüssel-Tag verwenden, wenn das Standard-Tag an einer anderen Stelle 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;
}

Überprüfen Sie für das Update jedes Element der Liste. Wenn eine Ansicht mit der angegebenen ID vorhanden ist, ist sie sichtbar, sodass wir das Update durchführen.

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

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

Ali
quelle
3

Holen Sie sich die Modellklasse zuerst als global wie dieses Modellklassenobjekt

SampleModel golbalmodel=new SchedulerModel();

und initialisiere es auf global

Rufen Sie die aktuelle Zeile der Ansicht vom Modell ab, indem Sie sie als globales Modell initialisieren

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

Setzen Sie den geänderten Wert auf die zu setzende globale Modellobjektmethode und fügen Sie den notifyDataSetChanged hinzu, der für mich funktioniert

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

Hier ist eine verwandte Frage dazu mit guten Antworten.

koteswara DK
quelle
Dies ist der richtige Ansatz, da Sie das Objekt erhalten, das Sie aktualisieren möchten, es aktualisieren und dann den Adapter benachrichtigen. Dadurch werden die Zeileninformationen mit den neuen Daten geändert.
Gastón Saillén
2

Die Antworten sind klar und richtig, ich werde hier eine Idee für den CursorAdapterFall hinzufügen .

Wenn Sie eine Unterklasse CursorAdapter(oder ResourceCursorAdapter, oder SimpleCursorAdapter) haben, können Sie entweder Methoden implementieren ViewBinderoder überschreiben, bindView()und newView()diese erhalten keinen aktuellen Listenelementindex in Argumenten. Wenn einige Daten eintreffen und Sie relevante sichtbare Listenelemente aktualisieren möchten, woher kennen Sie deren Indizes?

Meine Problemumgehung war:

  • Führen Sie eine Liste aller erstellten Listenelementansichten und fügen Sie dieser Liste Elemente hinzu newView()
  • Wenn Daten eintreffen, iterieren Sie sie und sehen Sie, welche aktualisiert werden müssen - besser als notifyDatasetChanged()alle zu aktualisieren und zu aktualisieren

Aufgrund des Ansichtsrecyclings entspricht die Anzahl der Ansichtsreferenzen, die ich speichern und iterieren muss, ungefähr der Anzahl der auf dem Bildschirm sichtbaren Listenelemente.

Pēteris Caune
quelle
2
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 verwenden Sie bitte diesen Code

Libin Thomas
quelle
Ich habe dies auf recycleView implementiert, es funktioniert. Aber wenn Sie eine Liste scrollen, ändert sich diese erneut. irgendeine Hilfe?
Fahid Nadeem
1

Ich habe den Code verwendet, der Erik bereitgestellt hat, funktioniert hervorragend, aber ich habe einen komplexen benutzerdefinierten Adapter für meine Listenansicht und wurde mit der zweimaligen Implementierung des Codes konfrontiert, der die Benutzeroberfläche aktualisiert. Ich habe versucht, die neue Ansicht von der getView-Methode meines Adapters abzurufen (die Arrayliste mit den Listenansichtsdaten wurde bereits 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 funktioniert gut, aber ich weiß nicht, ob dies eine gute Praxis ist. Ich muss also nicht den Code implementieren, der das Listenelement zweimal aktualisiert.

Primoz990
quelle
1

genau das habe ich benutzt

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);
    }
Chefish
quelle
Ich erhalte Ansicht als relatives Layout. Wie finde ich Textansicht im relativen Layout?
Girish
1

Ich habe eine andere Lösung wie die RecyclyerView- Methode void entwickelt notifyItemChanged(int position)und die CustomBaseAdapter- Klasse wie folgt erstellt :

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, auch eine CustomDataSetObservable-Klasse für eine mDataSetObservableVariable in CustomAdapterClass 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();
        }
    }
}

In der Klasse CustomBaseAdapter gibt es eine Methode notifyItemChanged(int position), und Sie können diese Methode aufrufen, wenn Sie eine Zeile aktualisieren möchten, wo immer Sie möchten (vom Klicken auf die Schaltfläche oder an einer beliebigen Stelle, an der Sie diese Methode aufrufen möchten). Und voila!, Ihre einzelne Zeile wird sofort aktualisiert.

Aprido Sandyasa
quelle
0

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

Richtig - oldData size == neue Datengröße und alte Daten-IDs und deren Reihenfolge == neue Daten-IDs und Reihenfolge

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

Ignorieren Sie den letzten Parameter für bindView (ich verwende ihn, um festzustellen, ob ich Bitmaps für ImageDrawable recyceln muss oder nicht).

Wie oben erwähnt, entspricht die Gesamtzahl der "sichtbaren" Ansichten ungefähr der Menge, die auf den Bildschirm passt (ohne Berücksichtigung von Orientierungsänderungen usw.).

JRun
quelle
0

Zusätzlich zu dieser Lösung ( https://stackoverflow.com/a/3727813/5218712 ) möchten Sie nur hinzufügen, dass sie nur funktionieren sollte, wenn listView.getChildCount() == yourDataList.size(); in ListView eine zusätzliche Ansicht vorhanden sein könnte.

Beispiel für das Auffüllen der untergeordneten Elemente:  listView.mChildren Array

Svyatoslav Ruzhitsky
quelle