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.