Como atualizar os dados do adaptador RecyclerView?

275

Tentando descobrir qual é o problema com RecyclerViewo adaptador da atualização .

Depois de receber uma nova lista de produtos, tentei:

  1. Atualize a ArrayListpartir do fragmento em que recyclerViewé criado, configure novos dados para o adaptador e chame adapter.notifyDataSetChanged(); não funcionou.

  2. Crie um novo adaptador, como outros fizeram, e funcionou para eles, mas nenhuma alteração para mim: recyclerView.setAdapter(new RecyclerViewAdapter(newArrayList))

  3. Crie um método no Adapterqual atualize os dados da seguinte maneira:

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

    Então eu chamo esse método sempre que quiser atualizar a lista de dados; não funcionou.

  4. Para verificar se eu posso modificar o recyclerView de alguma forma e tentei remover pelo menos um item:

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

    Tudo permaneceu como estava.

Aqui está o meu 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 da RecyclerViewseguinte forma:

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

Então, como atualizo os dados do adaptador para exibir os itens recém-recebidos?


Atualização: o problema era que o layout em que gridView era o seguinte:

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

Então eu apenas removi LinearLayoute fiz FrameLayoutcomo layout pai.

Filip Luchianenco
fonte
items.clear(); items.addAll(newItems);é um padrão feio. Se você realmente precisa de cópias defensivas aqui, items = new ArrayList(newItems);seria menos feio.
Miha_x64
2
@ miha-x64 Você está certo - seria menos feio. O problema é que isso simplesmente não funciona. O adaptador não obtém referência à referência. Ele obtém a própria referência. Portanto, se você criar um novo conjunto de dados, ele terá uma nova referência, enquanto o adaptador ainda conhecerá apenas o antigo.
A incrível Jan

Respostas:

326

Estou trabalhando com o RecyclerView e a remoção e a atualização funcionam bem.

1) REMOVER: Existem 4 etapas para remover um item de um RecyclerView

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

Essas linhas de códigos funcionam para mim.

2) ATUALIZAR OS DADOS: As únicas coisas que eu tinha que fazer é

mAdapter.notifyDataSetChanged();

Você precisava fazer tudo isso no código do Actvity / Fragment e não no código do RecyclerView Adapter.

Niccolò Passolunghi
fonte
2
Obrigado. Por favor, verifique a atualização. De alguma forma, o layout linear não deixa a lista de atualizações do RecylerView.
Filip Luchianenco
40
você só precisa destas duas linhas: list.remove (position); mAdapter.notifyItemRemoved (position);
feisal 10/0316
3
Para mim, as linhas recycler.removeViewAt(position);(ou melhor recycler.removeAllViews()) foram as cruciais.
MSpeed ​​16/03/19
2
Uma consideração importante para evitar falhas irritantes durante a exclusão: você não precisa chamar esta linha recycler.removeViewAt (position);
Angelo Nodari
7
NotifyDataSetChanged é um exagero, especialmente se você estiver lidando com MUITAS listas longas. Recarregar tudo para alguns itens? Não, obrigado. Além disso, recycler.removeViewAt (position)? Não é necessário. E não precisa chamar "mAdapter.notifyItemRemoved (position)" e "mAdapter.notifyItemRangeChanged (position, list.size ())". Remova alguns ou remova um. Notificar uma vez. Ligue para um deles.
Vitor Hugo Schwaab
329

Esta é uma resposta geral para futuros visitantes. As várias maneiras de atualizar os dados do adaptador são explicadas. O processo inclui duas etapas principais todas as vezes:

  1. Atualize o conjunto de dados
  2. Notifique o adaptador sobre a alteração

Inserir item único

Adicione "Pig" no índice 2.

Inserir item único

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

Inserir vários itens

Insira mais três animais no índice 2.

Inserir vários itens

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

Remover item único

Remova "Pig" da lista.

Remover item único

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

Remova vários itens

Remova "Camel" e "Sheep" da lista.

Remova vários itens

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

Remova todos os itens

Limpe a lista inteira.

Remova todos os itens

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

Substituir lista antiga por nova lista

Limpe a lista antiga e adicione uma nova.

Substituir lista antiga por nova lista

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

O adaptertem uma referência a data, por isso é importante que eu não tenha definido dataum novo objeto. Em vez disso, limpei os itens antigos datae os adicionei.

Atualizar item único

Mude o item "Ovelha" para que ele diga "Eu gosto de ovelha".

Atualizar item único

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

Mover item único

Mova "Ovelha" de uma posição 3para outra 1.

Mover item único

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

Aqui está o código do projeto para sua referência. O código do adaptador RecyclerView pode ser encontrado nesta resposta .

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

  • Se você usar notifyDataSetChanged(), nenhuma animação será executada. Também pode ser uma operação cara, portanto, não é recomendável usar notifyDataSetChanged()se você estiver atualizando apenas um único item ou um intervalo de itens.
  • Confira o DiffUtil se estiver fazendo alterações grandes ou complexas em uma lista.

Um estudo mais aprofundado

Suragch
fonte
5
Resposta incrível! Quais seriam as etapas ao substituir um item em, o que resultaria em um tipo de exibição diferente? É apenas notifyItemChanged ou uma remoção combinada seguida por uma inserção?
Semaphor
Com seu exemplo mínimo, parece que notifyItemChanged é suficiente. Ele chamará onCreateViewHolder e onBindViewHolder para o item alterado e a exibição diferente será mostrada. Eu tive um problema em um aplicativo que atualiza um item que resultaria em uma exibição diferente, mas parece haver outro problema.
Semaphor
@Suragch qualquer ideia sobre a minha pergunta: stackoverflow.com/questions/57332222/... Eu tentei de tudo listados neste tópico, mas sem sucesso :(
Que tal quando usamos o DiffUtils para fazer alterações? Eles não são tão fluidos como chamar manualmente .notify <Action> ().
GuilhE 19/03
1
Realmente derrubou a bola Update single item, deveria ter sido como tartarugas
ardnew
46

Isto é o que funcionou para mim:

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

Depois de criar um novo adaptador que contém a lista atualizada (no meu caso, era um banco de dados convertido em um ArrayList) e defini-lo como adaptador, tentei recyclerView.invalidate()e funcionou.

martinpkmn
fonte
12
isso não atualizaria a exibição inteira em vez de apenas atualizar os itens que foram alterados?
Twilly
13
Em vez de criar um novo adaptador a cada vez, o que fiz foi criar um método setter na minha classe de adaptador personalizado para definir a nova lista. Depois disso, basta ligar para YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();Dito isto, soa como o que o OP tentou primeiro (apenas adicionando isso como um comentário para que ele possa ajudar outra pessoa, como funcionou no meu caso).
Kevin Lee
2
Leitor, não aceite isso. Esta resposta funciona, mas há uma maneira mais eficiente. Em vez de recarregar todos os dados novamente , a maneira mais eficiente é adicionar os novos dados ao adaptador.
Sebastialonso
Sim, eu concordo com o @TWilly, ele redesenhará a exibição completa na interface do usuário.
Rahul
18

você tem 2 opções para fazer isso: atualize a interface do usuário do adaptador:

mAdapter.notifyDataSetChanged();

ou atualize-o a partir do recyclerView:

recyclerView.invalidate();
ediBersh
fonte
15

Outra opção é usar o diffutil . Ele comparará a lista original com a nova lista e usará a nova lista como atualização, se houver uma alteração.

Basicamente, podemos usar o DiffUtil para comparar os dados antigos e os novos e deixá-lo chamar notifyItemRangeRemoved, e notifyItemRangeChanged e notifyItemRangeInserted em seu nome.

Um exemplo rápido do uso de diffUtil em vez 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)

Eu faço o trabalho calculado com o thread principal, caso seja uma grande lista.

j2emanue
fonte
1
embora isso esteja correto, como atualizar os dados dentro do seu adaptador? Digamos que estou exibindo a Lista <Objeto> no adaptador e recebo uma nova atualização com a Lista <Objeto>. O DiffUtil calculará a diferença e a despachará no adaptador, mas não está fazendo nada com a lista original ou nova (no seu caso getItems(). Como você sabe quais dados da lista original precisam ser removidos / adicionados / atualizados?
vanomart
1
Talvez este artigo possa ajudar. .. medium.com/@nullthemall/…
j2emanue 12/01
@vanomart, você só precisa substituir diretamente o seu campo de lista local pelo novo valor da lista, como faria normalmente, a única diferença nessa abordagem é que, em vez de notifyDataSetChanged (), você usa o DiffUtil para atualizar a exibição.
precisa
alguma ideia sobre a minha? Eu tentei tudo listado aqui, mas sem sorte: stackoverflow.com/questions/57332222/…
Obrigado pela sua resposta! Você pode explicar por que devo colocar o "clear" e o "addAll" após o dispatchUpdatesTo ?? Tnx!
Adi Azarya 22/03
10

Atualizar dados de listview, gridview e recyclerview

mAdapter.notifyDataSetChanged();

ou

mAdapter.notifyItemRangeChanged(0, itemList.size());
Pankaj Talaviya
fonte
Isso não parece atualizar meus dados até que o usuário comece a rolar. Eu olhei em todo o lugar e não pode figura porque ..
SudoPlz
@SudoPlz você pode usar costume de rolagem ouvinte para detectar rolagem e executar a operação notificar como stackoverflow.com/a/31174967/4401692
Pankaj Talaviya
Obrigado Pankaj, mas é exatamente isso que estou tentando evitar. Quero atualizar tudo SEM que o usuário toque na tela.
SudoPlz 20/0318
5

A melhor e a maneira mais legal de adicionar novos dados aos dados atuais é

 ArrayList<String> newItems = new ArrayList<String>();
 newItems = getList();
 int oldListItemscount = alcontainerDetails.size();
 alcontainerDetails.addAll(newItems);           
 recyclerview.getAdapter().notifyItemChanged(oldListItemscount+1, al_containerDetails);
Rushi Ayyappa
fonte
2
Vejo pessoas usando o "notifyDataSetChanged" para TUDO, não é um exagero? Quero dizer, recarregar a lista inteira porque algumas foram alteradas ou foram adicionadas ... Eu gosto dessa abordagem, estava implementando agora, com o método "notifyItemRangeInserted".
Vitor Hugo Schwaab
5

Eu resolvi o mesmo problema de uma maneira diferente. Eu não tenho dados esperando por eles do thread em segundo plano, então comece com uma lista 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();
        }

É isso aí.

Ahmed Ozmaan
fonte
2

Esses métodos são eficientes e bons para começar a usar um 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();
}

Você pode implementá-los dentro de uma classe de adaptador ou em seu fragmento ou atividade, mas nesse caso você precisa instanciar o adaptador para chamar os métodos de notificação. No meu caso, eu normalmente o implico no adaptador.

Teocci
fonte
2

Eu descobri que uma maneira muito simples de recarregar o RecyclerView é apenas chamar

recyclerView.removeAllViews();

Isso removerá primeiro todo o conteúdo do RecyclerView e o adicionará novamente com os valores atualizados.

Jonathan Persgarden
fonte
2
Por favor, não use isso. Você estará pedindo problemas.
precisa saber é
1

eu recebi a resposta depois de muito tempo

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

Se nada mencionado nos comentários acima estiver funcionando para você. Isso pode significar que o problema está em outro lugar.

Um local em que encontrei a solução foi a maneira como defini a lista para o adaptador. Na minha atividade, a lista era uma variável de instância e eu a alterava diretamente quando qualquer dado era alterado. Por ser uma variável de referência, havia algo estranho acontecendo. Então, mudei a variável de referência para local e usei outra variável para atualizar os dados e depois passar para a addAll()função mencionada nas respostas acima.

bitSmith
fonte