Come posso aggiornare una singola riga in un ListView?


131

Ho un oggetto ListViewche mostra notizie. Contengono un'immagine, un titolo e del testo. L'immagine viene caricata in un thread separato (con una coda e tutto) e quando l'immagine viene scaricata, ora chiamo notifyDataSetChanged()l'adattatore elenco per aggiornare l'immagine. Funziona, ma getView()viene chiamato troppo frequentemente, poiché notifyDataSetChanged()chiama getView()tutti gli elementi visibili. Voglio aggiornare solo il singolo elemento nell'elenco. Come lo farei?

I problemi che ho con il mio approccio attuale sono:

  1. Lo scorrimento è lento
  2. Ho un'animazione dissolvenza sull'immagine che si verifica ogni volta che viene caricata una singola nuova immagine nell'elenco.

Risposte:


199

Ho trovato la risposta, grazie alle tue informazioni Michelle. Puoi davvero ottenere la vista giusta usando View#getChildAt(int index). Il problema è che inizia a contare dal primo elemento visibile. In effetti, puoi ottenere solo gli oggetti visibili. Lo risolvi con ListView#getFirstVisiblePosition().

Esempio:

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
nota a sé: mImgView.setImageURI () aggiornerà la voce dell'elenco ma solo una volta; quindi sto meglio usando mLstVwAdapter.notifyDataSetInvalidated () invece.
kellogs

Questa soluzione funziona. Ma questo può essere fatto solo con yourListView.getChildAt (indice); e cambia la vista. Entrambe le soluzioni funzionano !!
AndroidDev

cosa accadrebbe se chiamo getView () sull'adattatore solo se la posizione è tra getFirstVisiblePosition () e getLastVisiblePosition ()? funzionerebbe allo stesso modo? penso che aggiornerà la vista come al solito, giusto?
sviluppatore Android

Nel mio caso, alcune volte la vista è nulla, perché ogni elemento viene aggiornato in modo asincrono, meglio controllare se vista! = Null
Khawar

2
Funziona alla grande. Mi aiuta a completare il compito di implementare la funzione Mi piace in un'app simile a Instagram. Stavo usando un adattatore personalizzato, una trasmissione nel pulsante mi piace all'interno di un elemento della lista, lanciando un asincrono per dire al backend qualcosa di simile con un ricevitore di trasmissione sul frammento genitore, e infine la risposta di Erik per aggiornare la lista.
Josh,

71

Questa domanda è stata posta a Google I / O 2010, puoi guardarla qui:

Il mondo di ListView, tempo 52:30

Fondamentalmente ciò che Romain Guy spiega è quello di chiamare getChildAt(int)in ListViewper ottenere la vista e chiamata (credo) getFirstVisiblePosition()per scoprire la correlazione tra la posizione e l'indice.

Romain indica anche il progetto chiamato Shelves come esempio, penso che potrebbe significare il metodo ShelvesActivity.updateBookCovers(), ma non riesco a trovare la chiamata di getFirstVisiblePosition().

FANTASTICI AGGIORNAMENTI IN ARRIVO:

RecyclerView risolverà questo problema nel prossimo futuro. Come sottolineato su http://www.grokkingandroid.com/first-glance-androids-recyclerview/ , sarai in grado di chiamare i metodi per specificare esattamente la modifica, come ad esempio:

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

Inoltre, tutti vorranno utilizzare le nuove viste basate su RecyclerView perché saranno premiate con animazioni di bell'aspetto! Il futuro è fantastico! :-)


3
Buona cosa! :) Forse puoi aggiungere anche un segno di spunta qui - v potrebbe essere nullo se la vista non è disponibile al momento. E naturalmente questi dati spariranno se l'utente fa scorrere ListView, quindi si dovrebbero anche aggiornare i dati nell'adattatore (forse senza chiamare notificationDataSetChanged ()). In generale, penso che sarebbe una buona idea mantenere tutta questa logica all'interno dell'adattatore, ovvero passare ad esso il riferimento ListView.
mreichelt,

Questo ha funzionato per me, tranne - quando la vista ha lasciato lo schermo, quando rientra la modifica viene ripristinata. Immagino perché nell'anteprima sta ancora ricevendo le informazioni originali. Come posso aggirare questo?
Gloscherrybomb,

6

Ecco come l'ho fatto:

I tuoi articoli (righe) devono avere ID univoci in modo da poterli aggiornare in seguito. Imposta il tag di ogni vista quando l'elenco ottiene la vista dall'adattatore. (Puoi anche utilizzare il tag chiave se il tag predefinito viene utilizzato da qualche altra parte)

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

Per l'aggiornamento controlla ogni elemento dell'elenco, se è presente una vista con ID specificato, quindi eseguiamo l'aggiornamento.

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

In realtà è più facile di altri metodi e migliore quando hai a che fare con ID non posizioni! Inoltre è necessario chiamare l'aggiornamento per gli elementi che diventano visibili.


3

ottenere prima la classe del modello globale come questo oggetto classe del modello

SampleModel golbalmodel=new SchedulerModel();

e inizializzalo su globale

ottiene la riga corrente della vista dal modello inizializzandola al modello globale

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

imposta il valore modificato sul metodo dell'oggetto modello globale da impostare e aggiungi notifyDataSetChanged per me funziona

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

Ecco una domanda correlata su questo con buone risposte.


Questo è l'approccio giusto, poiché si ottiene l'oggetto che si desidera aggiornare, aggiornarlo e quindi notificare l'adattatore e ciò cambierà le informazioni sulla riga con i nuovi dati.
Gastón Saillén,

2

Le risposte sono chiare e corrette, aggiungerò un'idea per il CursorAdaptercaso qui.

Se si sta eseguendo la sottoclasse CursorAdapter(o ResourceCursorAdapter, o SimpleCursorAdapter), si arriva all'implementazione ViewBindero alla sostituzione bindView()e ai newView()metodi, questi non ricevono l'indice della voce di elenco corrente negli argomenti. Pertanto, quando arrivano alcuni dati e si desidera aggiornare gli elementi dell'elenco visibili pertinenti, come si conoscono i loro indici?

La mia soluzione era di:

  • mantenere un elenco di tutte le viste degli elementi dell'elenco create, aggiungere elementi a questo elenco da newView()
  • quando arrivano i dati, iterali e vedi quale è necessario aggiornare, meglio che fare notifyDatasetChanged()e aggiornare tutti

A causa del riciclaggio della vista, il numero di riferimenti della vista che dovrò memorizzare e iterare sarà approssimativamente uguale al numero di voci dell'elenco visibili sullo schermo.


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

Per RecycleView, utilizzare questo codice


ho implementato questo su recycleView, funziona. Ma quando si scorre l'elenco, si cambia di nuovo. qualsiasi aiuto?
Fahid Nadeem,

1

Ho usato il codice che ha fornito Erik, funziona benissimo, ma ho un adattatore personalizzato complesso per la mia visualizzazione elenco e mi sono trovato di fronte a due volte l'implementazione del codice che aggiorna l'interfaccia utente. Ho cercato di ottenere la nuova vista dal metodo getView dei miei adattatori (l'arraylist che contiene i dati listview è già stato aggiornato / modificato):

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

Funziona bene, ma non so se questa è una buona pratica. Quindi non ho bisogno di implementare il codice che aggiorna la voce di elenco due volte.


1

esattamente l'ho usato

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

Ricevo Visualizza come layout relativo come trovare Textview all'interno del layout relativo?
Girish,

1

Ho inventato un'altra soluzione, come il metodo RecyclyerView void notifyItemChanged(int position), per creare la classe CustomBaseAdapter in questo modo:

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

    }
}

Non dimenticare di creare anche una classe CustomDataSetObservable per la mDataSetObservablevariabile in CustomAdapterClass , in questo modo:

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

sulla classe CustomBaseAdapter esiste un metodo notifyItemChanged(int position)e puoi chiamarlo quando vuoi aggiornare una riga dove vuoi (dal pulsante o ovunque tu voglia chiamare quel metodo). E voilà !, la tua riga singola si aggiornerà all'istante ..


0

La mia soluzione: se è corretta *, aggiorna i dati e gli elementi visualizzabili senza rielaborare l'intero elenco. Altrimenti notificaDataSetChanged.

Corretto - oldData size == nuove dimensioni dei dati e vecchi ID dei dati e il loro ordine == nuovi ID dei dati e ordine

Come:

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

ed ovviamente:

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

Ignora l'ultimo parametro per bindView (lo uso per determinare se devo riciclare le bitmap per ImageDrawable).

Come accennato in precedenza, il numero totale di viste "visibili" è all'incirca la quantità che si adatta allo schermo (ignorando i cambiamenti di orientamento ecc.), Quindi non è un problema per la memoria.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.