Comment faire défiler vers le bas d'un RecyclerView? scrollToPosition ne fonctionne pas


161

Je voudrais faire défiler vers le bas de la liste RecyclerView après le chargement de l'activité.

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(...)lève une exception concernant la non prise en charge dans RecyclerView et conversationView.scrollToPosition(...)ne semble rien faire.

Après le bloc de code ci-dessus, j'ai ajouté

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

ce qui ne marche pas. Il y a 30 éléments dans GENERIC_MESSAGE_LIST.


Appelez-vous scrollToPositionimmédiatement après avoir configuré l'adaptateur?
ianhanniballake

@ianhanniballake oui, c'est juste après conversationView.setAdapter(conversationViewAdapter);.
waylaidwanderer

4
C'est probablement parce que vous appelez +1 au lieu de -1 donc le 5ème élément serait la position [4] car il commence à 0. Faire +1 vous donnerait un ArrayOutOfBounds ... ne s'est-il pas écrasé là?
RCB

1
Ajouter après setadapter mRecyclerView.scrollToPosition (carsList.size () - 1);
Webserveis le

Réponses:


196

Réglez simplement setStackFromEnd=trueousetReverseLayout=true pour que LLM mettra en page les éléments de la fin.

La différence entre ces deux est que setStackFromEnd la vue affichera le dernier élément, la direction de la disposition restera la même. (Ainsi, dans une vue de recyclage horizontale de gauche à droite, le dernier élément sera affiché et le défilement vers la gauche affichera les éléments précédents)

Alors que setReverseLayoutchangera l'ordre des éléments ajoutés par l'adaptateur. La mise en page commencera à partir du dernier élément, qui sera le plus à gauche dans une vue LTR Recycler, puis le défilement vers la droite affichera les éléments précédents.

Échantillon:

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

Consultez la documentation pour plus de détails.


133
Cela peut être utile, mais cela ne semble pas répondre à la question qui a été posée.
joseph

6
Si vous définissez à la fois stackFromEnd = true et setReverseLayout = true, cela ne fonctionne pas. Quelqu'un connaît-il une solution de contournement?
Luccas Correa

10
Pour la vue de chat linearLayoutManager.setStackFromEnd (true) fonctionne.
Muneeb Mirza

7
Cela ne répond pas du tout à la question.
Orbit le

1
Ne fonctionne que jusqu'à ce que la vue soit pleine. Il n'a pas l'intention de défiler, il suffit d'empiler du bas.
MiguelSlv

379

Je regardais ce post pour trouver la réponse mais ... Je pense que tout le monde sur ce post faisait face au même scénario que moi: a scrollToPosition()été totalement ignoré, pour une raison évidente.

Qu'est-ce que j'utilisais?

recyclerView.scrollToPosition(items.size());

... ce TRAVAILLE ?

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

6
recyclerView.scrollToPosition(messages.size()-1);fonctionne vraiment pour moi, je me demande juste la raison.
Moteur Bai

25
Cela a fonctionné pour moi. Cela a du sens car les listes Java sont basées sur zéro. Par exemple, la liste [0,1,2] a une taille de 3 mais la dernière position est 2 car elle commence par 0.
Calvin

19
FYI, pour un défilement fluide, il y en a smoothScrollToPosition(...).
Ivan Morgillo

5
@Ivan Morgilo smoothScrollToPosition ne fonctionne pas du tout: S
cesards

2
@IvanMorgillo - votre réponse semble mieux fonctionner à la fois avec le résultat souhaité et plus fluide = D. il semble que le défilement vers la position soit un peu bogué pour moi (je suppose qu'il y a un petit délai entre la fonction appelée et la création de la vue? de toute façon, cela ne fonctionne pas correctement)
Roee

54

Je sais qu'il est tard pour répondre ici, mais si quelqu'un veut savoir que la solution est ci-dessous

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

8
Cela devrait être conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() **-1**);comme les positions dans les listes sont basées sur zéro.
Berťák

2
Cela n'aidera pas si la hauteur de la dernière rangée est supérieure à la hauteur de l'écran.
Anuj Jindal

C'est ce dont j'avais besoin. Bien que je devais l'envelopper dans un postdelay pour que tout fonctionne correctement.
Marc Alexander

18

Pour faire défiler vers le bas depuis n'importe quelle position dans la vue de recyclage

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

Ajoutez ce code après l'envoi du message et avant de recevoir le message du serveur

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

10

Lorsque vous appelez setAdapter, cela ne met pas immédiatement en page et ne positionne pas les éléments sur l'écran (cela prend une seule passe de mise en page), par conséquent, votre scrollToPosition()appel n'a aucun élément réel vers lequel faire défiler lorsque vous l'appelez.

Au lieu de cela, vous devez enregistrer un ViewTreeObserver.OnGlobalLayoutListener (via addOnGlobalLayoutListner () à partir d'un ViewTreeObservercréé par conversationView.getViewTreeObserver()) qui retarde votre scrollToPosition()jusqu'à après le premier passage de mise en page:

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
Merci, mais cela ne fonctionne pas. Il interrompt le défilement en arrêtant de fonctionner, et si vous faites glisser, il défile étrangement rapidement.
waylaidwanderer

On dirait que vous ne supprimez pas l'auditeur. Il ne doit être appelé qu'une seule fois (juste après la première mise en page des éléments) et pas du tout pendant le défilement.
ianhanniballake

OnGlobalLayoutListenerprobablement pas supprimé parce que vous recherchez vto.isAlive(). Je ne connais pas la raison, mais ViewTreeObserverparfois est changé pour un View, donc au lieu de vérifier pour isAlivevous, mieux vaut simplement vous mettre au courant ViewTreeObserveravecconversationView.getViewTreeObserver().removeGlobalOnLayoutListener
Dmitry Zaytsev

@ianhanniballake J'ai proposé une modification pour résoudre ce problème.
Saket

7

Solution pour Kotlin :

appliquer le code ci-dessous après avoir défini "recyclerView.adapter" ou après "recyclerView.adapter.notifyDataSetChanged ()"

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

5

À Kotlin:

recyclerView.viewTreeObserver.addOnGlobalLayoutListener { scrollToEnd() }

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

Hah, merci pour GlobalLayoutListener! Après refactoring, j'ai dû utiliser votre méthode pour faire défiler. N'oubliez pas de supprimer l'auditeur comme dans stackoverflow.com/questions/28264139/… , ou vous ne ramènerez pas RecyclerView au début.
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));
    }
  }
}

et

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

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

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

le code ci-dessus fonctionne, mais ce n'est pas fluide et pas cool.


4

SI vous avez un adaptateur connecté avec recyclerView, faites-le simplement.

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


4

La réponse Roc est d'une grande aide. Je voudrais y ajouter un petit bloc:

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

4

Dans mon cas où les vues n'ont pas la même hauteur, appeler scrollToPosition sur le LayoutManager a fonctionné pour vraiment faire défiler vers le bas et voir pleinement le dernier élément:

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

3

Défilement initial lors de la première entrée dans la vue du recycleur, puis utilisation

linearLayoutManager.scrollToPositionWithOffset(messageHashMap.size()-1

mettre moins pour faire défiler vers le bas pour faire défiler vers le haut mettre en valeur positive);

si la vue est très grande en hauteur, le décalage particulier de scrolltoposition est utilisé pour le haut de la vue, vous utilisez

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

2

Cela fonctionne parfaitement bien pour moi:

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

0

Seule la réponse d'Ian a pu faire mon RecyclerViewparchemin à une position spécifiée. Cependant, le RecyclerViewn'a pas pu faire défiler après quand j'ai utilisé scrollToPosition(). smoothScrollToPosition()fonctionnait mais l'animation initiale le rendait trop lent lorsque la liste était longue. La raison était que l'auditeur n'a pas été supprimé. J'ai utilisé le code ci-dessous pour supprimer le courant ViewTreeObserveret cela a fonctionné comme un charme.

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

0

ce code vous donnera d'abord le dernier message, je pense que cette réponse est 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

J'ai essayé une méthode de @galex , cela a fonctionné jusqu'à la refactorisation. J'ai donc utilisé une réponse de @yanchenko et j'ai changé un peu. C'est probablement parce que j'ai appelé scrolling from onCreateView(), où une vue fragmentée a été construite (et n'avait probablement pas la bonne taille).

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

Vous pouvez également ajouter un chèque viewTreeObserver.isAlivecomme sur https://stackoverflow.com/a/39001731/2914140 .


0

Si aucun de ceux-ci ne fonctionnait,

vous devriez essayer d'utiliser:

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

En faisant cela, vous demandez un certain ConstraintLayout (ou ce que vous avez) à afficher. Le parchemin est instantané.

Je travaille même avec le clavier montré.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.