¿Cómo puedo actualizar una sola fila en una ListView?


Tengo un ListView que muestra las noticias. Contienen una imagen, un título y algo de texto. La imagen se carga en un hilo separado (con una cola y todo) y cuando se descarga la imagen, ahora llamo a notifyDataSetChanged() en el adaptador de lista para actualizar la imagen. Esto funciona, pero getView() se llama con demasiada frecuencia, ya que notifyDataSetChanged() llama a getView() para todos los elementos visibles. Quiero actualizar solo el elemento de la lista. ¿Cómo haría esto?

Problemas que tengo con mi enfoque actual son:

  1. El desplazamiento es lento
  2. Tengo una animación de fundido en la imagen que sucede cada vez que se carga una sola imagen nueva en la lista.
Author: Alan Stokes, 2010-09-16

10 answers

Encontré la respuesta, gracias a su información Michelle. De hecho, puede obtener la vista correcta usando View#getChildAt(int index). El problema es que comienza a contar desde el primer elemento visible. De hecho, solo puede obtener los elementos visibles. Esto se resuelve con ListView#getFirstVisiblePosition().

Ejemplo:

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!");
}
 188
Author: Erik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-08 13:30:49

Esta pregunta se ha hecho en el Google I / O 2010, se puede ver aquí:

El mundo de ListView, tiempo 52:30

Básicamente lo que explica Romain Guy es llamar getChildAt(int) en el ListView para obtener la vista y (creo) llamar getFirstVisiblePosition() para averiguar la correlación entre la posición y el índice.

Romain también señala el proyecto llamado Estantes como un ejemplo, creo que podría significar el método ShelvesActivity.updateBookCovers(), pero no puedo encuentra la llamada de getFirstVisiblePosition().

ACTUALIZACIONES IMPRESIONANTES QUE VIENEN:

El RecyclerView arreglará esto en un futuro cercano. Como se señaló en http://www.grokkingandroid.com/first-glance-androids-recyclerview / , podrá llamar a métodos para especificar exactamente el cambio, como:

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

Además, todo el mundo querrá utilizar las nuevas vistas basadas en RecyclerView porque serán recompensados con animaciones de aspecto agradable! El futuro se ve impresionante! :-)

 68
Author: mreichelt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-02 21:29:12

Así es como lo hice:

Sus elementos (filas) deben tener ID únicos para que pueda actualizarlos más tarde. La etiqueta de cada vista cuando la lista es la vista desde el adaptador. (También puede usar la etiqueta key si la etiqueta predeterminada se usa en otro lugar)

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

Para la actualización, compruebe cada elemento de la lista, si hay una vista con un id dado, es visible, por lo que realizamos la actualización.

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

En realidad es más fácil que otros métodos y mejor cuando se trata de ids no posiciones! También debe llamar a update para los elementos que se hacen visibles.

 6
Author: Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-09 09:52:29

Las respuestas son claras y correctas, agregaré una idea para CursorAdapter case aquí.

Si está subclasificando CursorAdapter (o ResourceCursorAdapter, o SimpleCursorAdapter), entonces puede implementar ViewBinder o anular bindView() y newView() métodos, estos no reciben el índice de elementos de lista actual en los argumentos. Por lo tanto, cuando llegan algunos datos y desea actualizar los elementos relevantes de la lista visible, ¿cómo conoce sus índices?

Mi solución fue:

  • mantenga una lista de todas las vistas de elementos de lista creadas, agregue items to this list from newView()
  • cuando lleguen los datos, itérelos y vea cuál necesita actualizarse better mejor que hacerlo notifyDatasetChanged() y actualizarlos todos

Debido al reciclaje de la vista, el número de referencias de la vista que necesitaré almacenar e iterar será aproximadamente igual al número de elementos de la lista visibles en la pantalla.

 2
Author: Pēteris Caune,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-21 19:44:57
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);

Para RecycleView utilice este código

 2
Author: Libin Thomas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-16 18:30:47

Obtenga la clase del modelo primero como global como este objeto de clase del modelo

SampleModel golbalmodel=new SchedulerModel();

E inicializarlo a global

Obtener la fila actual de la vista por el modelo inicializando el it al modelo global

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

Establezca el valor cambiado en el método objeto modelo global que se establecerá y agregue el notifyDataSetChanged its works para mí

golbalmodel.setStartandenddate(changedate);

notifyDataSetChanged();

Aquí hay una pregunta relacionada con esto con buenas respuestas.

 2
Author: koteswara D K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:24

Usé el código que proporcionó Erik, funciona muy bien, pero tengo un adaptador personalizado complejo para mi listview y me enfrenté a la implementación dos veces del código que actualiza la interfaz de usuario. He intentado obtener la nueva vista de mi método getView de adaptadores (la arraylist que contiene los datos de listview ya se ha actualizado/cambiado):

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

Está funcionando bien, pero no sé si esta es una buena práctica. Así que no necesito implementar el código que actualiza el elemento de la lista dos veces.

 1
Author: Primoz990,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-25 12:20:08

Exactamente usé esto

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);
    }
 1
Author: chefish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-13 13:01:20

Mi solución: Si es correcto*, actualice los datos y los elementos visibles sin volver a dibujar toda la lista. De lo contrario notifyDataSetChanged.

Correct-oldData size == new data size, and old data IDs and their order == new data IDs and order

Cómo:

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

Y por supuesto:

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

Ignore el último parámetro de bindView (lo uso para determinar si necesito o no reciclar mapas de bits para ImageDrawable).

Como se mencionó anteriormente, el número total de vistas' visibles' es aproximadamente la cantidad que cabe en la pantalla (ignorando los cambios de orientación, etc.), por lo que no es un problema de memoria.

 0
Author: JRun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-25 07:07:35

Inventé otra solución, como RecyclyerView método void notifyItemChanged(int position), crear CustomBaseAdapter clase así:

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

    }
}

No olvides crear una clase CustomDataSetObservable también para mDataSetObservable variable en CustomAdapterClass , así:

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

En la clase CustomBaseAdapter hay un método notifyItemChanged(int position), y puede llamar a ese método cuando desee actualizar una fila donde quiera (desde el botón haga clic o en cualquier lugar que desee llamar eso método). Y voila!, su sola fila se actualizará al instante..

 0
Author: Aprido Sandyasa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-23 05:59:23