Como rolar para a parte inferior de um RecyclerView? scrollToPosition não funciona

161

Gostaria de rolar para o final da lista do RecyclerView depois de carregar a atividade.

GENERIC_MESSAGE_LIST = (ArrayList) intent.getExtras().getParcelableArrayList(ConversationsAdapter.EXTRA_MESSAGE);
conversationView = (RecyclerView) findViewById(R.id.list_messages);
conversationView.setHasFixedSize(true);
conversationViewLayoutManager = new LinearLayoutManager(this);
conversationView.setLayoutManager(conversationViewLayoutManager);
conversationViewAdapter = new ConversationAdapter(GENERIC_MESSAGE_LIST, this);
conversationView.setAdapter(conversationViewAdapter);

conversationView.scrollTo(...)lança uma exceção sobre não ser suportado no RecyclerView e conversationView.scrollToPosition(...)parece não fazer nada.

Após o bloco de código acima, adicionei

conversationView.scrollToPosition(GENERIC_MESSAGE_LIST.size() + 1)

o que não funciona. Existem 30 elementos em GENERIC_MESSAGE_LIST.

waylaidwanderer
fonte
Você está ligando scrollToPositionimediatamente após configurar o adaptador?
Ianhanniballake
@ianhanniballake sim, é logo depois conversationView.setAdapter(conversationViewAdapter);.
waylaidwanderer
4
Provavelmente porque você está chamando +1 em vez de -1, o quinto elemento seria a posição [4] porque começa em 0. Fazer +1 daria a você um ArrayOutOfBounds ... não travou lá?
RCB 12/02
1
Adicionar após setadapter mRecyclerView.scrollToPosition (carsList.size () - 1);
Webserveis

Respostas:

194

Basta definir setStackFromEnd=trueou setReverseLayout=truepara que o LLM faça o layout dos itens do final.

A diferença entre esses dois é que setStackFromEnddefinirá a visualização para mostrar o último elemento, a direção do layout permanecerá a mesma. (Portanto, em uma Visualização de reciclagem horizontal da esquerda para a direita, o último elemento será mostrado e a rolagem para a esquerda mostrará os elementos anteriores)

Considerando que setReverseLayoutirá alterar a ordem dos elementos adicionados pelo adaptador. O layout será iniciado a partir do último elemento, que será o mais à esquerda em uma Visualização de Reciclador LTR e, em seguida, rolar para a direita exibirá os elementos anteriores.

Amostra:

final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
linearLayoutManager.setReverseLayout(true);
_listView.setLayoutManager(linearLayoutManager);

Consulte a documentação para obter detalhes.

yigit
fonte
132
Isso pode ser útil, mas parece não responder à pergunta que foi feita.
Joseph
6
Se você definir stackFromEnd = true e setReverseLayout = true, não funcionará. Alguém conhece alguma solução alternativa?
Luccas Correa 22/01
10
Para visualização de bate-papo, linearLayoutManager.setStackFromEnd (true) funciona.
Muneeb Mirza
7
Isso não resolve a questão.
Orbit
1
Só funciona até a visualização estar cheia. Não tem a intenção de rolar, apenas empilhar a partir do fundo.
MiguelSlv
376

Eu estava olhando para este post para encontrar a resposta, mas ... acho que todos neste post estavam enfrentando o mesmo cenário que eu: scrollToPosition()foi totalmente ignorado, por uma razão evidente.

O que eu estava usando?

recyclerView.scrollToPosition(items.size());

... o que funcionou ?

recyclerView.scrollToPosition(items.size() - 1);
Roc Boronat
fonte
6
recyclerView.scrollToPosition(messages.size()-1);realmente funciona para mim, eu me pergunto o motivo.
Engine Bai
25
Isso funcionou para mim. Na verdade, faz sentido porque as listas Java são baseadas em zero. Por exemplo, a lista [0,1,2] tem um tamanho de 3, mas a última posição é 2 porque começa com 0.
Calvin
19
Para sua informação, para uma rolagem suave, existe smoothScrollToPosition(...).
Ivan Morgillo 17/07/2015
5
@Ivan Morgilo smoothScrollToPosition não funciona de jeito nenhum: S
cesards
2
@IvanMorgillo - sua resposta parece funcionar melhor tanto com o resultado desejado quanto mais suave = D. Parece que de rolagem para a posição é um pouco buggy para mim (o meu palpite é que há um pequeno atraso entre a função chamado e a criação da vista de qualquer maneira ele não está funcionando corretamente?)
Roee
54

Eu sei que é tarde para responder aqui, ainda assim, se alguém quiser saber a solução está abaixo

conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() - 1);
WritingForAnroid
fonte
8
Deve ser conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() **-1**);como as posições nas listas são baseadas em zero.
Berťák
2
Isso não ajudará se a altura da última linha for maior que a altura da tela.
Anuj Jindal
Era disso que eu precisava. Embora eu tivesse que envolvê-lo em um manipulador pós-atraso para fazer tudo funcionar corretamente.
21420 Marc
18

Para deslocar-se de qualquer posição na vista da reciclagem para baixo

edittext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                rv.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                      rv.scrollToPosition(rv.getAdapter().getItemCount() - 1);
                    }
                }, 1000);
            }
        });
Muhammad Umair Shafique
fonte
15

Adicione este código depois de enviar a mensagem e antes de receber a mensagem do servidor

recyclerView.scrollToPosition(mChatList.size() - 1);
Shubham Agrawal
fonte
10

Quando você liga setAdapter, isso não coloca e posiciona imediatamente os itens na tela (que recebe uma única passagem de layout); portanto, sua scrollToPosition()ligação não possui elementos reais para a rolagem quando você liga.

Em vez disso, você deve registrar um ViewTreeObserver.OnGlobalLayoutListener (via addOnGlobalLayoutListner () de um ViewTreeObservercriado por conversationView.getViewTreeObserver()) que atrasa seu scrollToPosition()até após a primeira passagem de layout:

conversationView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  public void onGlobalLayout() {
    conversationView.scrollToPosition(GENERIC_MESSAGE_LIST.size();
    // Unregister the listener to only call scrollToPosition once
    conversationView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

    // Use vto.removeOnGlobalLayoutListener(this) on API16+ devices as 
    // removeGlobalOnLayoutListener is deprecated.
    // They do the same thing, just a rename so your choice.
  }
});
ianhanniballake
fonte
1
Obrigado, mas isso não funciona. Ele interrompe a rolagem ao interromper o trabalho e, se você arrastar, ele rolará estranhamente rápido.
waylaidwanderer
Parece que você não está removendo o ouvinte. Só deve ser chamado exatamente uma vez (logo após a disposição dos elementos) e não durante a rolagem.
precisa saber é o seguinte
OnGlobalLayoutListenerprovavelmente não será removido porque você está procurando vto.isAlive(). Eu não sei a razão, mas ViewTreeObserveràs vezes é alterado para um View, então em vez de verificar para isAliveque você melhor apenas obter atual ViewTreeObservercomconversationView.getViewTreeObserver().removeGlobalOnLayoutListener
Dmitry Zaytsev
@ianhanniballake Propus uma edição para corrigir esse problema.
Saket
6

Solução para Kotlin :

aplique o código abaixo após a configuração "recyclerView.adapter" ou após "recyclerView.adapter.notifyDataSetChanged ()"

recyclerView.scrollToPosition(recyclerView.adapter.itemCount - 1)
AbhinayMe
fonte
5

Em Kotlin:

recyclerView.viewTreeObserver.addOnGlobalLayoutListener { scrollToEnd() }

private fun scrollToEnd() =
        (adapter.itemCount - 1).takeIf { it > 0 }?.let(recyclerView::smoothScrollToPosition)
yanchenko
fonte
Hah, obrigado por GlobalLayoutListener! Após refatorar, tive que usar seu método para rolar. Não se esqueça de remover o ouvinte como em stackoverflow.com/questions/28264139/… , ou você não rolará o RecyclerView de volta ao início.
CoolMind 22/05/19
4
class MyLayoutManager extends LinearLayoutManager {

  public MyLayoutManager(Context context) {
    super(context, LinearLayoutManager.VERTICAL, false);
  }

  @Override public void smoothScrollToPosition(RecyclerView recyclerView,
      final RecyclerView.State state, final int position) {

    int fcvip = findFirstCompletelyVisibleItemPosition();
    int lcvip = findLastCompletelyVisibleItemPosition();

    if (position < fcvip || lcvip < position) {
      // scrolling to invisible position

      float fcviY = findViewByPosition(fcvip).getY();
      float lcviY = findViewByPosition(lcvip).getY();

      recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

        int currentState = RecyclerView.SCROLL_STATE_IDLE;

        @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

          if (currentState == RecyclerView.SCROLL_STATE_SETTLING
              && newState == RecyclerView.SCROLL_STATE_IDLE) {

            // recursive scrolling
            smoothScrollToPosition(recyclerView, state, position);
          }

          currentState = newState;
        }

        @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

          int fcvip = findFirstCompletelyVisibleItemPosition();
          int lcvip = findLastCompletelyVisibleItemPosition();

          if ((dy < 0 && fcvip == position) || (dy > 0 && lcvip == position)) {
            // stop scrolling
            recyclerView.setOnScrollListener(null);
          }
        }
      });

      if (position < fcvip) {
        // scroll up

        recyclerView.smoothScrollBy(0, (int) (fcviY - lcviY));
      } else {
        // scroll down

        recyclerView.smoothScrollBy(0, (int) (lcviY - fcviY));
      }
    } else {
      // scrolling to visible position

      float fromY = findViewByPosition(fcvip).getY();
      float targetY = findViewByPosition(position).getY();

      recyclerView.smoothScrollBy(0, (int) (targetY - fromY));
    }
  }
}

e

MyLayoutManager layoutManager = new MyLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);

RecyclerView.Adapter adapter = new YourAdapter();
recyclerView.setAdapter(adapter);

recyclerView.smoothScrollToPosition(adapter.getItemCount() - 1);

O código acima funciona, mas não é suave e não é legal.

Yuki Yoshida
fonte
4

Se você tiver um adaptador conectado ao recyclerView, faça isso.

mRecyclerView.smoothScrollToPosition(mRecyclerView.getAdapter().getItemCount());

Wasim Amin
fonte
4

A resposta do Roc é uma grande ajuda. Eu gostaria de adicionar um pequeno bloco a ele:

mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1);
abedfar
fonte
4

No meu caso, em que as visualizações não têm a mesma altura, chamar scrollToPosition no LayoutManager funcionou para realmente rolar para baixo e ver completamente o último item:

recycler.getLayoutManager().scrollToPosition(adapter.getItemCount() - 1);
galex
fonte
2

Isso funciona perfeitamente bem para mim:

AdapterChart adapterChart = new AdapterChart(getContext(),messageList);
recyclerView.setAdapter(adapterChart);
recyclerView.scrollToPosition(recyclerView.getAdapter().getItemCount()-1);
amaresh Hiremani
fonte
2

Role pela primeira vez ao entrar na exibição do reciclador pela primeira vez e use

linearLayoutManager.scrollToPositionWithOffset(messageHashMap.size()-1

colocar em menos para rolar para baixo e rolar para cima em valor positivo);

se a vista tiver uma altura muito grande, o deslocamento específico da topologia de rolagem será usado na parte superior da vista.

int overallXScroldl =chatMessageBinding.rvChat.computeVerticalScrollOffset();
chatMessageBinding.rvChat.smoothScrollBy(0, Math.abs(overallXScroldl));
Dishant Kawatra
fonte
0

Somente a resposta de Ian foi capaz de fazer meu RecyclerViewpergaminho para uma posição especificada. No entanto, RecyclerViewnão foi possível rolar depois quando eu usei scrollToPosition(). smoothScrollToPosition()funcionou, mas a animação inicial tornou muito lenta quando a lista era longa. O motivo foi que o ouvinte não foi removido. Usei o código abaixo para remover a corrente ViewTreeObservere funcionou como um encanto.

    mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            mRecyclerView.scrollToPosition(mPosition);
            mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    });
Anky An
fonte
0

esse código fornecerá a última postagem primeiro, acho que essa resposta é útil.

    mInstaList=(RecyclerView)findViewById(R.id.insta_list);
    mInstaList.setHasFixedSize(true);

    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    layoutManager.setOrientation(LinearLayoutManager.VERTICAL);

    mInstaList.setLayoutManager(layoutManager);
    layoutManager.setStackFromEnd(true);
    layoutManager.setReverseLayout(true);
hasanga lakdinu
fonte
0

Tentei um método de @galex , funcionou até a refatoração. Então, usei uma resposta de @yanchenko e mudei um pouco. Provavelmente, isso é porque chamei rolagem de onCreateView(), onde uma exibição de fragmento foi construída (e provavelmente não tinha o tamanho certo).

private fun scrollPhotosToEnd(view: View) {
    view.recycler_view.viewTreeObserver.addOnGlobalLayoutListener(object :
        ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                view.recycler_view.viewTreeObserver.removeOnGlobalLayoutListener(this)
            } else {
                @Suppress("DEPRECATION")
                view.recycler_view.viewTreeObserver.removeGlobalOnLayoutListener(this)
            }
            adapter?.itemCount?.takeIf { it > 0 }?.let {
                view.recycler_view.scrollToPosition(it - 1)
            }
        }
    })
}

Você também pode adicionar uma verificação viewTreeObserver.isAlivesemelhante em https://stackoverflow.com/a/39001731/2914140 .

CoolMind
fonte
0

Se nada disso funcionou,

você deve tentar usar:

ConstraintLayout targetView = (ConstraintLayout) recyclerView.findViewHolderForAdapterPosition(adapter.getItemCount()-1).itemView;
targetView.getParent().requestChildFocus(targetView, targetView);

Ao fazer isso, você está solicitando que um determinado ConstraintLayout (ou o que você tenha) seja exibido. O pergaminho é instantâneo.

Eu trabalho mesmo com o teclado mostrado.

Arnaud
fonte