Atualizando dados no RecyclerView e mantendo sua posição de rolagem

96

Como atualizar os dados exibidos em RecyclerView(chamando notifyDataSetChangedseu adaptador) e certificar-se de que a posição de rolagem seja redefinida exatamente onde estava?

No caso do bom e velho ListView, basta recuperar getChildAt(0), verificar getTop()e chamar setSelectionFromTopcom os mesmos dados exatos depois.

Não parece ser possível no caso de RecyclerView.

Acho que devo usar seu, LayoutManagerque realmente fornece scrollToPositionWithOffset(int position, int offset), mas qual é a maneira correta de recuperar a posição e o deslocamento?

layoutManager.findFirstVisibleItemPosition()e layoutManager.getChildAt(0).getTop()?

Ou existe uma maneira mais elegante de fazer o trabalho?

Konrad Morawski
fonte
Trata-se dos valores de largura e altura de seu RecyclerView ou LayoutManager. Verifique esta solução: stackoverflow.com/questions/28993640/…
serefakyuz
@Konard, no meu caso, tenho uma lista de arquivos de áudio com implementação do Seekbar e executando com o seguinte problema: 1) Para alguns últimos itens indexados, logo que disparou o notificadorItemChanged (pos) push view na parte inferior automática. 2) Enquanto mantém o notifyItemChanged (pos) ele travou na lista e não permite rolar facilmente. Embora eu faça uma pergunta, deixe-me saber se você tem alguma abordagem melhor para resolver esse problema.
CoDe

Respostas:

139

Eu uso este. ^ _ ^

// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();

// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);

É mais simples, espero que ajude você!

DawnYu
fonte
1
Esta é realmente a resposta certa imo. Fica complicado apenas por causa do carregamento de dados assíncrono, mas você pode adiar a restauração.
EpicPandaForce
perfeito +2 exatamente o que eu estava procurando
Adeel Turk
@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m estou tentando implementar isso, mas não está funcionando, o que estou fazendo de errado?
Kaustubh Bhagwat
@KaustubhBhagwat Talvez você deva marcar "recyclerViewState! = Null" antes de usá-lo.
DawnYu
4
Onde exatamente devo usar este código? no método onResume?
Lucky_girl
47

Eu tenho um problema bastante semelhante. E eu vim com a seguinte solução.

Usar notifyDataSetChangedé uma má ideia. Você deve ser mais específico, então RecyclerViewsalvará o estado de rolagem para você.

Por exemplo, se você só precisa atualizar, ou em outras palavras, deseja que cada visualização seja religada, basta fazer o seguinte:

adapter.notifyItemRangeChanged(0, adapter.getItemCount());
Kaerdan
fonte
2
Tentei adapter.notifyItemRangeChanged (0, adapter.getItemCount ()) ;, que também funciona como .notifyDataSetChanged ()
Mani,
4
Isso ajudou no meu caso. Eu estava inserindo vários itens na posição 0 e com notifyDataSetChangeda posição de rolagem mudaria mostrando os novos itens. No entanto, se usar notifyItemRangeInserted(0, newItems.size() - 1)o, RecyclerViewmantém o estado de rolagem.
Carlosph
muito muito boa resposta, estou buscando solução dessa resposta um dia inteiro. muito obrigado ..
Gundu Bandgar
adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); deve ser evitado, conforme indicado no último Google I / O (assista à apresentação na visualização de reciclagem dentro e fora), seria melhor (se você tiver o conhecimento) informar a visualização de reciclagem exatamente quais itens foram alterados em vez de uma atualização geral simples de todas as visualizações.
A. Steenbergen
3
não está funcionando, recicle a atualização para o topo, embora eu tenha adicionado
Shaahul
21

EDITAR: Para restaurar exatamente a mesma posição aparente , como em, fazer com que pareça exatamente como estava, precisamos fazer algo um pouco diferente (veja abaixo como restaurar o valor exato de scrollY):

Salve a posição e desloque assim:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
int firstItem = manager.findFirstVisibleItemPosition();
View firstItemView = manager.findViewByPosition(firstItem);
float topOffset = firstItemView.getTop();

outState.putInt(ARGS_SCROLL_POS, firstItem);
outState.putFloat(ARGS_SCROLL_OFFSET, topOffset);

E então restaure o pergaminho assim:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);

Isso restaura a lista à sua posição aparente exata . Aparente porque terá a mesma aparência para o usuário, mas não terá o mesmo valor scrollY (devido a possíveis diferenças nas dimensões do layout paisagem / retrato).

Observe que isso só funciona com LinearLayoutManager.

--- Abaixo, como restaurar o scrollY exato, o que provavelmente fará a lista parecer diferente ---

  1. Aplique um OnScrollListener assim:

    private int mScrollY;
    private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            mScrollY += dy;
        }
    };

Isso armazenará a posição de rolagem exata o tempo todo em mScrollY.

  1. Armazene essa variável em seu pacote e restaure-a na restauração do estado para uma variável diferente , vamos chamá-la de mStateScrollY.

  2. Após a restauração do estado e depois que o RecyclerView redefinir todos os seus dados, redefina a rolagem com isto:

    mRecyclerView.scrollBy(0, mStateScrollY);

É isso aí.

Cuidado, que você restaure a rolagem para uma variável diferente, isso é importante, porque o OnScrollListener será chamado com .scrollBy () e, subsequentemente, definirá mScrollY com o valor armazenado em mStateScrollY. Se você não fizer isso, mScrollY terá o dobro do valor de rolagem (porque OnScrollListener funciona com deltas, não com rolagens absolutas).

A economia de estado em atividades pode ser alcançada desta forma:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(ARGS_SCROLL_Y, mScrollY);
}

E para restaurar, chame isso em seu onCreate ():

if(savedState != null){
    mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}

O salvamento do estado em fragmentos funciona de maneira semelhante, mas o salvamento do estado real precisa de um pouco mais de trabalho, mas há muitos artigos que tratam disso, então você não deve ter problemas para descobrir como, os princípios de salvar o pergaminho. e restaurá-lo permanece o mesmo.

A. Steenbergen
fonte
não entendo você pode postar um exemplo completo?
Este é, na verdade, o mais próximo de um exemplo completo que você pode obter se não quiser que eu poste toda a minha atividade
A. Steenbergen
Eu não entendo nada. Se houver itens adicionados no início da lista, não será errado permanecer na mesma posição de rolagem, já que agora você estará olhando para diferentes itens que ocuparam esta área?
desenvolvedor Android
Sim, nesse caso você deve ajustar a posição ao restaurar, no entanto, isso não faz parte da questão, pois normalmente ao girar você não adiciona ou remove itens. Isso seria estranho e um comportamento e provavelmente não do interesse do usuário, exceto em alguns casos especiais que podem existir, que não consigo imaginar, honestamente.
A. Steenbergen
9

Sim, você pode resolver esse problema tornando o construtor do adaptador apenas uma vez. Estou explicando a parte de codificação aqui:

if (appointmentListAdapter == null) {
        appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
        appointmentRecyclerView.setAdapter(appointmentListAdapter);

    } else {
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.notifyDataSetChanged();
    }

Agora você pode ver que verifiquei se o adaptador é nulo ou não e só inicializo quando ele é nulo.

Se o adaptador não for nulo, tenho certeza de que inicializei meu adaptador pelo menos uma vez.

Portanto, irei apenas adicionar a lista ao adaptador e chamar notificardatasetchanged.

O RecyclerView sempre mantém a última posição rolada, portanto, você não precisa armazenar a última posição, apenas chame notifikdatasetchanged, o recycler view sempre atualiza os dados sem ir para o topo.

Obrigado Happy Coding

Amit Singh Tomar
fonte
Acho que isso vai para a resposta aceita @Konrad Morawski
Jithin Jude
5

Mantenha a posição de rolagem usando a resposta @DawnYu para embrulhar notifyDataSetChanged()assim:

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
Morten Holmgaard
fonte
perfeito ... a melhor resposta
jlandyr
Há muito tempo que procuro esta resposta. Muito obrigado.
Alaa AbuZarifa
1

A primeira resposta de @DawnYu funciona, mas a visualização de reciclagem primeiro rola para o topo, depois volta para a posição de rolagem pretendida, causando uma reação de "tremulação" que não é agradável.

Para atualizar o recyclerView, especialmente depois de sair de outra atividade, sem piscar e manter a posição de rolagem, você precisa fazer o seguinte.

  1. Certifique-se de atualizar a visualização do reciclador usando DiffUtil. Leia mais sobre isso aqui: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Ao retomar sua atividade, ou no ponto em que deseja atualizá-la, carregue os dados em sua tela de reciclagem. Usando o diffUtil, apenas as atualizações serão feitas na tela de reciclagem, mantendo sua posição.

Espero que isto ajude.

Moses Mwongela
fonte
0

Eu não usei o Recyclerview, mas fiz isso no ListView. Amostra de código em Recyclerview:

setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        rowPos = mLayoutManager.findFirstVisibleItemPosition();

É o ouvinte quando o usuário está rolando. A sobrecarga de desempenho não é significativa. E a primeira posição visível é precisa dessa maneira.

O Android original
fonte
Ok, findFirstVisibleItemPositionretorna "a posição do adaptador da primeira visão visível", mas e o deslocamento.
Konrad Morawski
1
Existe um método semelhante para Recyclerview para método setSelection de ListView para a posição visível? Isso é o que eu fiz para ListView.
The Original Android
1
Que tal usar o método setScrollY com o parâmetro dy, um valor que você salvará? Se funcionar, atualizarei minha resposta. Eu não usei o Recyclerview, mas quero usá-lo em meu futuro aplicativo.
The Original Android
Por favor, veja minha resposta abaixo sobre como salvar a posição de rolagem.
A. Steenbergen
1
Tudo bem, vou manter isso em mente da próxima vez, eu não votei contra você com malintent. No entanto, discordo das respostas curtas e incompletas porque, quando comecei, as respostas curtas em que outros desenvolvedores assumiam muitas informações de base não me ajudaram muito, porque muitas vezes eram muito incompletas e eu tive que fazer muitas perguntas de esclarecimento, algo que você geralmente não pode ser feito em postagens stackoverflow antigas. Observe também como nenhuma resposta foi aceita por Konrad, o que indica que essas respostas não resolveram seu problema. Embora eu entenda seu ponto geral.
A. Steenbergen
-1
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

A solução aqui é continuar rolando a exibição de reciclagem quando uma nova mensagem chegar.

O método onChanged () detecta a ação executada em recyclerview.

Abhishek Chudekar
fonte
-1

Isso está funcionando para mim em Kotlin.

  1. Crie o adaptador e entregue seus dados no construtor
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. altere esta propriedade e chame notificarDataSetChanged ()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

O deslocamento de rolagem não muda.

Chris8447
fonte
-2

Tive esse problema com uma lista de itens em que cada um tinha um tempo em minutos até que estivessem "vencidos" e precisassem ser atualizados. Eu atualizaria os dados e depois ligaria

orderAdapter.notifyDataSetChanged();

e sempre rolaria para o topo. Eu substituí isso por

 for(int i = 0; i < orderArrayList.size(); i++){
                orderAdapter.notifyItemChanged(i);
            }

e foi bom. Nenhum dos outros métodos neste tópico funcionou para mim. No entanto, ao usar esse método, ele fez cada item individual piscar quando foi atualizado, então eu também tive que colocar isso no onCreateView do fragmento pai

RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
    if (animator instanceof SimpleItemAnimator) {
        ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
    }
Matt
fonte
-2

Se você tiver um ou mais EditTexts dentro de itens de uma visualização de reciclagem , desative o foco automático deles, colocando esta configuração na visualização pai de uma visualização de reciclagem:

android:focusable="true"
android:focusableInTouchMode="true"

Tive esse problema quando comecei outra atividade lançada a partir de um item de visualização de reciclagem, quando voltei e configurei uma atualização de um campo em um item com notificarItemChanged (posição) a rolagem de movimentos de RV e minha conclusão foi que, o autofoco de EditText Itens, o código acima resolveu meu problema.

melhor.

Akhha8
fonte
-3

Apenas retorne se a posição e posição antigas forem iguais;

private int oldPosition = -1;

public void notifyItemSetChanged(int position, boolean hasDownloaded) {
    if (oldPosition == position) {
        return;
    }
    oldPosition = position;
    RLog.d(TAG, " notifyItemSetChanged :: " + position);
    DBMessageModel m = mMessages.get(position);
    m.setVideoHasDownloaded(hasDownloaded);
    notifyItemChanged(position, m);
}
Raditya Gumay
fonte