¿Cómo actualizar los datos del adaptador RecyclerView?

275

Intentando averiguar cuál es el problema con la actualización RecyclerViewdel Adaptador.

Después de obtener una nueva Lista de productos, intenté:

  1. Actualice el ArrayListdesde el fragmento donde recyclerViewse crea, establezca nuevos datos en el adaptador, luego llame adapter.notifyDataSetChanged(); No funcionó.

  2. Cree un nuevo adaptador, como lo hicieron otros, y funcionó para ellos, pero no hay cambio para mí: recyclerView.setAdapter(new RecyclerViewAdapter(newArrayList))

  3. Cree un método en el Adapterque actualice los datos de la siguiente manera:

    public void updateData(ArrayList<ViewModel> viewModels) {
       items.clear();
       items.addAll(viewModels);
       notifyDataSetChanged();
    }
    

    Luego llamo a este método cada vez que quiero actualizar la lista de datos; No funcionó.

  4. Para verificar si puedo modificar el recyclerView de alguna manera, e intenté eliminar al menos un elemento:

     public void removeItem(int position) {
        items.remove(position);
        notifyItemRemoved(position);
    }
    

    Todo quedó como estaba.

Aquí está mi adaptador:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> implements View.OnClickListener {

    private ArrayList<ViewModel> items;
    private OnItemClickListener onItemClickListener;

    public RecyclerViewAdapter(ArrayList<ViewModel> items) {
        this.items = items;
    }


    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false);
        v.setOnClickListener(this);
        return new ViewHolder(v);
    }

    public void updateData(ArrayList<ViewModel> viewModels) {
        items.clear();
        items.addAll(viewModels);
        notifyDataSetChanged();
    }
    public void addItem(int position, ViewModel viewModel) {
        items.add(position, viewModel);
        notifyItemInserted(position);
    }

    public void removeItem(int position) {
        items.remove(position);
        notifyItemRemoved(position);
    }



    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ViewModel item = items.get(position);
        holder.title.setText(item.getTitle());
        Picasso.with(holder.image.getContext()).load(item.getImage()).into(holder.image);
        holder.price.setText(item.getPrice());
        holder.credit.setText(item.getCredit());
        holder.description.setText(item.getDescription());

        holder.itemView.setTag(item);
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    @Override
    public void onClick(final View v) {
        // Give some time to the ripple to finish the effect
        if (onItemClickListener != null) {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    onItemClickListener.onItemClick(v, (ViewModel) v.getTag());
                }
            }, 0);
        }
    }

    protected static class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView image;
        public TextView price, credit, title, description;

        public ViewHolder(View itemView) {
            super(itemView);
            image = (ImageView) itemView.findViewById(R.id.image);
            price = (TextView) itemView.findViewById(R.id.price);
            credit = (TextView) itemView.findViewById(R.id.credit);
            title = (TextView) itemView.findViewById(R.id.title);
            description = (TextView) itemView.findViewById(R.id.description);
        }
    }

    public interface OnItemClickListener {

        void onItemClick(View view, ViewModel viewModel);

    }
}

E inicio de la RecyclerViewsiguiente manera:

recyclerView = (RecyclerView) view.findViewById(R.id.recycler);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 5));
adapter = new RecyclerViewAdapter(items);
adapter.setOnItemClickListener(this);
recyclerView.setAdapter(adapter);

Entonces, ¿cómo actualizo los datos del adaptador para mostrar los elementos recién recibidos?


Actualización: el problema era que el diseño donde se veía gridView era el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:tag="catalog_fragment"
    android:layout_height="match_parent">

    <FrameLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"/>

        <ImageButton
            android:id="@+id/fab"
            android:layout_gravity="top|end"
            style="@style/FabStyle"/>

    </FrameLayout>
</LinearLayout>

Luego simplemente eliminé LinearLayoute hice FrameLayoutcomo diseño principal.

Filip Luchianenco
fuente
items.clear(); items.addAll(newItems);Es un patrón feo. Si realmente necesita una copia defensiva aquí, items = new ArrayList(newItems);sería menos feo.
Miha_x64
2
@ miha-x64 Tienes razón: sería menos feo. El problema es que esto simplemente no funciona. El adaptador no obtiene referencia a la referencia. Obtiene la referencia en sí. Por lo tanto, si crea un nuevo conjunto de datos, tiene una nueva referencia, mientras que el adaptador solo conoce el antiguo.
El increíble Jan

Respuestas:

326

Estoy trabajando con RecyclerView y tanto la eliminación como la actualización funcionan bien.

1) ELIMINAR: hay 4 pasos para eliminar un elemento de un RecyclerView

list.remove(position);
recycler.removeViewAt(position);
mAdapter.notifyItemRemoved(position);                 
mAdapter.notifyItemRangeChanged(position, list.size());

Esta línea de códigos me funciona.

2) ACTUALIZAR LOS DATOS: lo único que tuve que hacer es

mAdapter.notifyDataSetChanged();

Tuviste que hacer todo esto en el código Actvity / Fragment no en el código del adaptador RecyclerView.

Niccolò Passolunghi
fuente
2
Gracias. Por favor revise la actualización. De alguna manera, el diseño lineal no permite que la lista de actualizaciones de RecylerView.
Filip Luchianenco
40
solo necesita estas dos líneas: list.remove (posición); mAdapter.notifyItemRemoved (posición);
Feisal
3
Para mí, las líneas recycler.removeViewAt(position);(o más bien recycler.removeAllViews()) fueron las cruciales.
MSpeed
2
Una consideración importante para evitar problemas técnicos molestos durante la eliminación: no es necesario llamar a esta línea recycler.removeViewAt (posición);
Angelo Nodari
77
NotifyDataSetChanged es exagerado, especialmente si se trata de listas MUY largas. ¿Recargar todo por unos pocos artículos? No, gracias. Además, recycler.removeViewAt (posición)? Innecesario. Y no necesita llamar a "mAdapter.notifyItemRemoved (position)" y "mAdapter.notifyItemRangeChanged (position, list.size ())". Eliminar algunos, o eliminar uno. Notificar una vez. Llama a uno de ellos.
Vitor Hugo Schwaab
329

Esta es una respuesta general para futuros visitantes. Se explican las diversas formas de actualizar los datos del adaptador. El proceso incluye dos pasos principales cada vez:

  1. Actualizar el conjunto de datos
  2. Notificar al adaptador del cambio

Insertar elemento individual

Agregue "Pig" en el índice 2.

Insertar elemento individual

String item = "Pig";
int insertIndex = 2;
data.add(insertIndex, item);
adapter.notifyItemInserted(insertIndex);

Insertar elementos múltiples

Inserte tres animales más en el índice 2.

Insertar elementos múltiples

ArrayList<String> items = new ArrayList<>();
items.add("Pig");
items.add("Chicken");
items.add("Dog");
int insertIndex = 2;
data.addAll(insertIndex, items);
adapter.notifyItemRangeInserted(insertIndex, items.size());

Eliminar un solo artículo

Eliminar "Pig" de la lista.

Eliminar un solo artículo

int removeIndex = 2;
data.remove(removeIndex);
adapter.notifyItemRemoved(removeIndex);

Eliminar varios elementos

Elimina "Camel" y "Sheep" de la lista.

Eliminar varios elementos

int startIndex = 2; // inclusive
int endIndex = 4;   // exclusive
int count = endIndex - startIndex; // 2 items will be removed
data.subList(startIndex, endIndex).clear();
adapter.notifyItemRangeRemoved(startIndex, count);

Eliminar todos los artículos

Borra toda la lista.

Eliminar todos los artículos

data.clear();
adapter.notifyDataSetChanged();

Reemplazar lista anterior con lista nueva

Borre la lista anterior y luego agregue una nueva.

Reemplazar lista anterior con lista nueva

// clear old list
data.clear();

// add new list
ArrayList<String> newList = new ArrayList<>();
newList.add("Lion");
newList.add("Wolf");
newList.add("Bear");
data.addAll(newList);

// notify adapter
adapter.notifyDataSetChanged();

El adaptertiene una referencia a data, por lo que es importante que no se estableció dataa un nuevo objeto. En cambio, borré los elementos antiguos datay luego agregué los nuevos.

Actualizar elemento individual

Cambie el elemento "Ovejas" para que diga "Me gustan las ovejas".

Actualizar elemento individual

String newValue = "I like sheep.";
int updateIndex = 3;
data.set(updateIndex, newValue);
adapter.notifyItemChanged(updateIndex);

Mover un solo elemento

Mueva "Sheep" de una posición 3a otra 1.

Mover un solo elemento

int fromPosition = 3;
int toPosition = 1;

// update data array
String item = data.get(fromPosition);
data.remove(fromPosition);
data.add(toPosition, item);

// notify adapter
adapter.notifyItemMoved(fromPosition, toPosition);

Código

Aquí está el código del proyecto para su referencia. El código del adaptador RecyclerView se puede encontrar en esta respuesta .

MainActivity.java

public class MainActivity extends AppCompatActivity implements MyRecyclerViewAdapter.ItemClickListener {

    List<String> data;
    MyRecyclerViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // data to populate the RecyclerView with
        data = new ArrayList<>();
        data.add("Horse");
        data.add("Cow");
        data.add("Camel");
        data.add("Sheep");
        data.add("Goat");

        // set up the RecyclerView
        RecyclerView recyclerView = findViewById(R.id.rvAnimals);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
                layoutManager.getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);
        adapter = new MyRecyclerViewAdapter(this, data);
        adapter.setClickListener(this);
        recyclerView.setAdapter(adapter);
    }

    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(this, "You clicked " + adapter.getItem(position) + " on row number " + position, Toast.LENGTH_SHORT).show();
    }

    public void onButtonClick(View view) {
        insertSingleItem();
    }

    private void insertSingleItem() {
        String item = "Pig";
        int insertIndex = 2;
        data.add(insertIndex, item);
        adapter.notifyItemInserted(insertIndex);
    }

    private void insertMultipleItems() {
        ArrayList<String> items = new ArrayList<>();
        items.add("Pig");
        items.add("Chicken");
        items.add("Dog");
        int insertIndex = 2;
        data.addAll(insertIndex, items);
        adapter.notifyItemRangeInserted(insertIndex, items.size());
    }

    private void removeSingleItem() {
        int removeIndex = 2;
        data.remove(removeIndex);
        adapter.notifyItemRemoved(removeIndex);
    }

    private void removeMultipleItems() {
        int startIndex = 2; // inclusive
        int endIndex = 4;   // exclusive
        int count = endIndex - startIndex; // 2 items will be removed
        data.subList(startIndex, endIndex).clear();
        adapter.notifyItemRangeRemoved(startIndex, count);
    }

    private void removeAllItems() {
        data.clear();
        adapter.notifyDataSetChanged();
    }

    private void replaceOldListWithNewList() {
        // clear old list
        data.clear();

        // add new list
        ArrayList<String> newList = new ArrayList<>();
        newList.add("Lion");
        newList.add("Wolf");
        newList.add("Bear");
        data.addAll(newList);

        // notify adapter
        adapter.notifyDataSetChanged();
    }

    private void updateSingleItem() {
        String newValue = "I like sheep.";
        int updateIndex = 3;
        data.set(updateIndex, newValue);
        adapter.notifyItemChanged(updateIndex);
    }

    private void moveSingleItem() {
        int fromPosition = 3;
        int toPosition = 1;

        // update data array
        String item = data.get(fromPosition);
        data.remove(fromPosition);
        data.add(toPosition, item);

        // notify adapter
        adapter.notifyItemMoved(fromPosition, toPosition);
    }
}

Notas

  • Si lo usa notifyDataSetChanged(), no se realizará ninguna animación. Esto también puede ser una operación costosa, por lo que no se recomienda su uso notifyDataSetChanged()si solo está actualizando un solo elemento o una gama de elementos.
  • Consulte DiffUtil si está realizando cambios grandes o complejos en una lista.

Estudio adicional

Suragch
fuente
55
Respuesta impresionante! ¿Cuáles serían los pasos al reemplazar un elemento in, lo que daría como resultado un tipo de vista diferente? ¿Es solo notifyItemChanged o una eliminación combinada seguida de una inserción?
Semaphor
Con su ejemplo mínimo, parece que notifyItemChanged es suficiente. Llamará a onCreateViewHolder y onBindViewHolder para el elemento modificado y se mostrará la vista diferente. Tuve un problema en una aplicación que actualizaba un elemento que daría como resultado una vista diferente, pero parece haber otro problema.
Semaphor
@Suragch alguna idea sobre mi pregunta: stackoverflow.com/questions/57332222/… Intenté todo lo que aparece en este hilo pero fue en vano :(
¿Qué tal cuando usamos DiffUtils para hacer cambios? No son tan fluidos como llamar manualmente .notify <Action> ().
GuilhE
1
Realmente dejé caer la pelota Update single item, debería haber sido como las tortugas
ardnew
46

Esto es lo que funcionó para mí:

recyclerView.setAdapter(new RecyclerViewAdapter(newList));
recyclerView.invalidate();

Después de crear un nuevo adaptador que contiene la lista actualizada (en mi caso, era una base de datos convertida en una ArrayList) y configurarlo como adaptador, lo intenté recyclerView.invalidate()y funcionó.

martinpkmn
fuente
12
¿No actualizaría esto la vista completa en lugar de simplemente actualizar los elementos que han cambiado?
TWilly
13
En lugar de hacer un nuevo adaptador cada vez, lo que hice fue crear un método setter en mi clase de adaptador personalizado para establecer la nueva lista. Después de eso, solo llame a YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();Dicho esto, parece que lo que el OP intentó primero (solo agregue esto como un comentario para que pueda ayudar a alguien más, ya que eso funcionó en mi caso).
Kevin Lee
2
Lector, no te conformes con esto. Esta respuesta funciona, pero hay una manera más eficiente. En lugar de volver a cargar todos los datos nuevamente , la forma más eficiente es simplemente agregar los nuevos datos al adaptador.
Sebastialonso
Sí, estoy de acuerdo con @TWilly, volverá a dibujar la vista completa en la interfaz de usuario.
Rahul
18

tiene 2 opciones para hacer esto: actualizar la interfaz de usuario desde el adaptador:

mAdapter.notifyDataSetChanged();

o actualizarlo desde el reciclador Ver sí mismo:

recyclerView.invalidate();
ediBersh
fuente
15

Otra opción es usar diffutil . Comparará la lista original con la nueva lista y usará la nueva lista como la actualización si hay un cambio.

Básicamente, podemos usar DiffUtil para comparar los datos antiguos con los nuevos y dejar que llame a notifyItemRangeRemoved, y notifyItemRangeChanged y notifyItemRangeInserted en su nombre.

Un ejemplo rápido del uso de diffUtil en lugar de notifyDataSetChanged:

DiffResult diffResult = DiffUtil
                .calculateDiff(new MyDiffUtilCB(getItems(), items));

//any clear up on memory here and then
diffResult.dispatchUpdatesTo(this);

//and then, if necessary
items.clear()
items.addAll(newItems)

Hago el trabajo CalculateDiff fuera del hilo principal en caso de que sea una lista grande.

j2emanue
fuente
1
aunque esto es correcto, ¿cómo actualizo los datos dentro de su adaptador? Digamos que estoy mostrando List <Object> en el adaptador y obtengo una nueva actualización con List <Object>. DiffUtil calculará una diferencia y la enviará en el adaptador, pero no está haciendo nada con la lista original o nueva (en su caso getItems(). ¿Cómo sabe qué datos de la lista original deben eliminarse / agregarse / actualizarse?
vanomart
1
Quizás este artículo pueda ayudar. .. medium.com/@nullthemall/…
j2emanue
@vanomart solo tiene que reemplazar directamente su campo de lista local con el nuevo valor de la lista como lo haría normalmente, la única diferencia en este enfoque es que en lugar de notifyDataSetChanged () usa DiffUtil para actualizar la vista.
carrizo
alguna idea sobre la mia?
¡Gracias por su respuesta! ¿Puede explicar por qué debería poner "clear" y "addAll" después de dispatchUpdatesTo ?? Tnx!
Adi Azarya
10

Actualizar datos de vista de lista, vista de cuadrícula y vista de reciclado

mAdapter.notifyDataSetChanged();

o

mAdapter.notifyItemRangeChanged(0, itemList.size());
Pankaj Talaviya
fuente
Esto no parece actualizar mis datos hasta que el usuario comienza a desplazarse. He buscado por todas partes y no puedo entender por qué ...
SudoPlz
@SudoPlz puede usar un escucha de desplazamiento personalizado para detectar el desplazamiento y realizar su operación de notificación como stackoverflow.com/a/31174967/4401692
Pankaj Talaviya
Gracias Pankaj, pero eso es exactamente lo que estoy tratando de evitar. Quiero actualizar todo SIN que el usuario toque la pantalla.
SudoPlz
5

La mejor y la mejor manera de agregar nuevos datos a los datos actuales es

 ArrayList<String> newItems = new ArrayList<String>();
 newItems = getList();
 int oldListItemscount = alcontainerDetails.size();
 alcontainerDetails.addAll(newItems);           
 recyclerview.getAdapter().notifyItemChanged(oldListItemscount+1, al_containerDetails);
Rushi Ayyappa
fuente
2
Veo personas que usan el "notifyDataSetChanged" para TODO, ¿no es una exageración? Quiero decir, vuelva a cargar toda la lista porque se modificaron o se agregaron algunas ... Me gusta este enfoque, se estaba implementando en este momento, con el método "notifyItemRangeInserted".
Vitor Hugo Schwaab
5

He resuelto el mismo problema de una manera diferente. No tengo datos, los estoy esperando desde el hilo de fondo, así que comience con una lista de emty.

        mAdapter = new ModelAdapter(getContext(),new ArrayList<Model>());
   // then when i get data

        mAdapter.update(response.getModelList());
  //  and update is in my adapter

        public void update(ArrayList<Model> modelList){
            adapterModelList.clear(); 
            for (Product model: modelList) {
                adapterModelList.add(model); 
            }
           mAdapter.notifyDataSetChanged();
        }

Eso es.

Ahmed Ozmaan
fuente
2

Estos métodos son eficientes y buenos para comenzar a usar un básico RecyclerView.

private List<YourItem> items;

public void setItems(List<YourItem> newItems)
{
    clearItems();
    addItems(newItems);
}

public void addItem(YourItem item, int position)
{
    if (position > items.size()) return;

    items.add(item);
    notifyItemInserted(position);
}

public void addMoreItems(List<YourItem> newItems)
{
    int position = items.size() + 1;
    newItems.addAll(newItems);
    notifyItemChanged(position, newItems);
}

public void addItems(List<YourItem> newItems)
{
    items.addAll(newItems);
    notifyDataSetChanged();
}

public void clearItems()
{
    items.clear();
    notifyDataSetChanged();
}

public void addLoader()
{
    items.add(null);
    notifyItemInserted(items.size() - 1);
}

public void removeLoader()
{
    items.remove(items.size() - 1);
    notifyItemRemoved(items.size());
}

public void removeItem(int position)
{
    if (position >= items.size()) return;

    items.remove(position);
    notifyItemRemoved(position);
}

public void swapItems(int positionA, int positionB)
{
    if (positionA > items.size()) return;
    if (positionB > items.size()) return;

    YourItem firstItem = items.get(positionA);

    videoList.set(positionA, items.get(positionB));
    videoList.set(positionB, firstItem);

    notifyDataSetChanged();
}

Puede implementarlos dentro de una Clase de adaptador o en su Fragmento o Actividad, pero en ese caso debe crear una instancia del Adaptador para llamar a los métodos de notificación. En mi caso, generalmente lo implemento en el adaptador.

Teocci
fuente
2

Descubrí que una forma realmente simple de recargar el RecyclerView es simplemente llamar

recyclerView.removeAllViews();

Esto eliminará primero todo el contenido de RecyclerView y luego lo agregará nuevamente con los valores actualizados.

Jonathan Persgarden
fuente
2
Por favor no use esto. Estarás pidiendo problemas.
dell116
1

recibí la respuesta después de mucho tiempo

  SELECTEDROW.add(dt);
                notifyItemInserted(position);
                SELECTEDROW.remove(position);
                notifyItemRemoved(position);
roufal
fuente
0

Si nada de lo mencionado en los comentarios anteriores funciona para usted. Podría significar que el problema se encuentra en otro lugar.

Un lugar donde encontré la solución fue en la forma en que estaba configurando la lista para el adaptador. En mi actividad, la lista era una variable de instancia y la estaba cambiando directamente cuando cambiaba cualquier dato. Debido a que es una variable de referencia, sucedía algo extraño. Así que cambié la variable de referencia a una local y usé otra variable para actualizar los datos y luego pasar aaddAll() función mencionada en las respuestas anteriores.

bitSmith
fuente