Come scorrere fino alla fine di un RecyclerView? scrollToPosition non funziona


161

Vorrei scorrere fino alla fine dell'elenco di RecyclerView dopo aver caricato l'attività.

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(...)genera un'eccezione per non essere supportato in RecyclerView e conversationView.scrollToPosition(...)non sembra fare nulla.

Dopo il blocco di codice sopra, ho aggiunto

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

che non funziona. Ci sono 30 elementi in GENERIC_MESSAGE_LIST.


Stai chiamando scrollToPositionsubito dopo aver impostato l'adattatore?
ianhanniballake,

@ianhanniballake sì, è subito dopo conversationView.setAdapter(conversationViewAdapter);.
Waylaidwanderer,

4
Probabilmente è perché stai chiamando +1 invece di -1, quindi il 5 ° elemento sarebbe position [4] perché inizia da 0. Fare +1 ti darebbe un ArrayOutOfBounds ... non si è schiantato lì?
RCB,

1
Aggiungi dopo setadapter mRecyclerView.scrollToPosition (carsList.size () - 1);
Webserveis,

Risposte:


195

Basta impostare setStackFromEnd=trueo setReverseLayout=truein modo che LLM sarà layout di oggetti dalla fine.

La differenza tra questi due è che setStackFromEndimposterà la vista per mostrare l'ultimo elemento, la direzione del layout rimarrà la stessa. (Quindi, in una vista di riciclatore orizzontale da sinistra a destra, verrà mostrato l'ultimo elemento e lo scorrimento verso sinistra mostrerà gli elementi precedenti)

Considerando setReverseLayoutche cambierà l'ordine degli elementi aggiunti dall'adattatore. Il layout inizierà dall'ultimo elemento, che sarà il più a sinistra in una vista Riciclatore LTR e quindi, scorrendo verso destra, verranno mostrati gli elementi precedenti.

Campione:

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

Vedere la documentazione per i dettagli.


133
Questo potrebbe essere qualcosa di utile, ma non sembra rispondere alla domanda che è stata posta.
giuseppe

6
Se imposti sia stackFromEnd = true che setReverseLayout = true non funziona. Qualcuno sa qualche soluzione?
Luccas Correa,

10
Per la visualizzazione della chat linearLayoutManager.setStackFromEnd (true) funziona.
Muneeb Mirza,

7
Questo non affronta affatto la domanda.
Orbita,

1
Funziona solo fino a quando la vista non è piena. Non ha intenzione di scorrere, basta impilarlo dal basso.
MiguelSlv il

378

Stavo guardando questo post per trovare la risposta ma ... Penso che tutti in questo post stessero affrontando lo stesso mio scenario: è scrollToPosition()stato completamente ignorato, per una ragione evidente.

Cosa stavo usando?

recyclerView.scrollToPosition(items.size());

... cosa LAVORATO ?

recyclerView.scrollToPosition(items.size() - 1);

6
recyclerView.scrollToPosition(messages.size()-1);funziona davvero per me, mi chiedo solo il motivo.
Motore Bai

25
Questo ha funzionato per me. In realtà ha senso perché gli elenchi Java sono basati su zero. Ad esempio, la lista [0,1,2] ha una dimensione di 3 ma l'ultima posizione è 2 perché inizia con 0.
Calvin

19
Cordiali saluti, per una pergamena liscia, c'è smoothScrollToPosition(...).
Ivan Morgillo,

5
@Ivan Morgilo smoothScrollToPosition non funziona affatto: S
cesards

2
@IvanMorgillo - la tua risposta sembra funzionare meglio sia con il risultato desiderato che più regolare = D. sembra che scorrere nella posizione sia un po 'difettoso per me (la mia ipotesi è che ci sia un piccolo ritardo tra la funzione chiamata e la creazione della vista? comunque non funziona correttamente)
Roee

54

So che è tardi per rispondere qui, sempre se qualcuno vuole sapere che la soluzione è sotto

conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() - 1);

8
Dovrebbe essere conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() **-1**);come posizioni nelle liste sono a base zero.
Berťák,

2
Questo non aiuta se l'altezza dell'ultima riga è maggiore dell'altezza dello schermo.
Anuj Jindal,

Questo è quello di cui avevo bisogno. Anche se ho dovuto avvolgerlo in un gestore post-ritardo per far funzionare tutto correttamente.
Marc Alexander

18

Per scorrere verso il basso da qualsiasi posizione nella vista riciclo verso il basso

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

15

Aggiungi questo codice dopo aver inviato il messaggio e prima di ricevere il messaggio dal server

recyclerView.scrollToPosition(mChatList.size() - 1);

10

Quando chiami setAdapter, questo non si posiziona immediatamente e posiziona gli elementi sullo schermo (che richiede un singolo passaggio di layout), quindi la tua scrollToPosition()chiamata non ha elementi reali a cui scorrere quando la chiami.

Invece, dovresti registrare un ViewTreeObserver.OnGlobalLayoutListener (via addOnGlobalLayoutListner () da un ViewTreeObservercreato da conversationView.getViewTreeObserver()) che ritarda il tuo scrollToPosition()fino a dopo il primo passaggio del 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.
  }
});

1
Grazie, ma non funziona. Si interrompe lo scorrimento interrompendo il funzionamento del lancio e, se si trascina, scorre in modo stranamente veloce.
waylaidwanderer,

Sembra che tu non stia rimuovendo l'ascoltatore. Dovrebbe essere chiamato esattamente una sola volta (subito dopo la prima disposizione degli elementi) e per niente durante lo scorrimento.
ianhanniballake,

OnGlobalLayoutListenerprobabilmente non viene rimosso perché stai verificando vto.isAlive(). Non so il motivo, ma ViewTreeObservera volte è cambiato per una View, quindi invece di controllare per isAlivemeglio solo ottenere corrente ViewTreeObserverconconversationView.getViewTreeObserver().removeGlobalOnLayoutListener
Dmitry Zaytsev

@ianhanniballake Ho proposto una modifica per risolvere questo problema.
Saket,

7

Soluzione per Kotlin :

applicare sotto il codice dopo aver impostato "recyclerView.adapter" o dopo "recyclerView.adapter.notifyDataSetChanged ()"

recyclerView.scrollToPosition(recyclerView.adapter.itemCount - 1)

5

A Kotlin:

recyclerView.viewTreeObserver.addOnGlobalLayoutListener { scrollToEnd() }

private fun scrollToEnd() =
        (adapter.itemCount - 1).takeIf { it > 0 }?.let(recyclerView::smoothScrollToPosition)

Ah grazie GlobalLayoutListener! Dopo il refactoring ho dovuto usare il tuo metodo per scorrere. Non dimenticare di rimuovere l'ascoltatore come in stackoverflow.com/questions/28264139/… , altrimenti non scorrerai RecyclerView all'inizio.
CoolMind

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

il codice sopra funziona, ma non è fluido e non è interessante.


4

Se hai un adattatore collegato con recyclerView, fai semplicemente questo.

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


4

La risposta di Roc è di grande aiuto. Vorrei aggiungere un piccolo blocco ad esso:

mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1);

4

Nel mio caso in cui le viste non hanno la stessa altezza, chiamando scrollToPosition sul LayoutManager ha funzionato per scorrere fino in fondo e vedere completamente l'ultimo elemento:

recycler.getLayoutManager().scrollToPosition(adapter.getItemCount() - 1);

3

Scorrere per la prima volta quando si accede alla vista Riciclatore la prima volta, quindi utilizzare

linearLayoutManager.scrollToPositionWithOffset(messageHashMap.size()-1

mettere in meno per scorrere verso il basso per scorrere verso l'alto inserire un valore positivo);

se la vista è molto grande in altezza, per la parte superiore della vista viene utilizzato l'offset della posizione di scorrimento, quindi si utilizza

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

2

Questo funziona perfettamente per me:

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

0

Solo la risposta di Ian è stata in grado di RecyclerViewscorrere verso una posizione specifica. Tuttavia, The RecyclerViewnon è stato in grado di scorrere successivamente quando l'ho usato scrollToPosition(). smoothScrollToPosition()ha funzionato ma l'animazione iniziale lo ha reso troppo lento quando l'elenco era lungo. Il motivo era che l'ascoltatore non è stato rimosso. Ho usato il codice qui sotto per rimuovere la corrente ViewTreeObservere ha funzionato come un fascino.

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

0

questo codice ti darà prima l'ultimo post, penso che questa risposta sia utile.

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

0

Ho provato un metodo di @galex , ha funzionato fino al refactoring. Quindi ho usato una risposta di @yanchenko e sono cambiato un po '. Probabilmente questo è perché ho chiamato scrolling onCreateView(), da cui è stata costruita una vista frammentata (e probabilmente non aveva le dimensioni giuste).

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

Puoi anche aggiungere un segno di spunta viewTreeObserver.isAlivecome in https://stackoverflow.com/a/39001731/2914140 .


0

Se nessuno di questi ha funzionato,

dovresti provare a usare:

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

In questo modo, stai richiedendo un determinato ConstraintLayout (o qualunque cosa tu abbia) da visualizzare. La pergamena è istantanea.

Lavoro anche con la tastiera mostrata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.