¿Cómo desplazarse hasta la parte inferior de un RecyclerView? scrollToPosition no funciona

161

Me gustaría desplazarme al final de la lista RecyclerView después de cargar la actividad.

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(...)lanza una excepción sobre no ser compatible con RecyclerView, y conversationView.scrollToPosition(...)no parece hacer nada.

Después del bloque de código anterior, agregué

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

que no funciona Hay 30 elementos en GENERIC_MESSAGE_LIST.

waylaidwanderer
fuente
¿Llama scrollToPositioninmediatamente después de configurar el adaptador?
ianhanniballake
@ianhanniballake sí, es justo después conversationView.setAdapter(conversationViewAdapter);.
waylaidwanderer
44
Probablemente es porque está llamando a +1 en lugar de -1, por lo que el quinto elemento sería la posición [4] porque comienza en 0. Hacer +1 le daría un ArrayOutOfBounds ... ¿no se bloqueó allí?
RCB
1
Agregar después de setadapter mRecyclerView.scrollToPosition (carsList.size () - 1);
Webserveis

Respuestas:

194

Simplemente establezca setStackFromEnd=trueo más setReverseLayout=truepara que LLM diseñe los elementos desde el final.

La diferencia entre estos dos es que setStackFromEndestablecerá la vista para mostrar el último elemento, la dirección de diseño seguirá siendo la misma. (Por lo tanto, en una vista de reciclador horizontal de izquierda a derecha, se mostrará el último elemento y el desplazamiento hacia la izquierda mostrará los elementos anteriores)

Mientras setReverseLayoutque cambiará el orden de los elementos agregados por el Adaptador. El diseño comenzará desde el último elemento, que será el más a la izquierda en una Vista de Reciclador LTR y luego, al desplazarse hacia la derecha, se mostrarán los elementos anteriores.

Muestra:

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

Consulte la documentación para más detalles.

yigit
fuente
132
Esto podría ser algo útil, pero no parece responder a la pregunta que se hizo.
Joseph
66
Si establece stackFromEnd = true y setReverseLayout = true, no funciona. ¿Alguien sabe alguna solución?
Luccas Correa
10
Para la vista de chat, linearLayoutManager.setStackFromEnd (true) funciona.
Muneeb Mirza
77
Esto no aborda la pregunta en absoluto.
Orbit
1
Solo funciona hasta que la vista esté llena. No intenta desplazarse, solo apilar desde la parte inferior.
MiguelSlv
376

Estaba mirando esta publicación para encontrar la respuesta, pero ... Creo que todos en esta publicación se enfrentaban al mismo escenario que yo: scrollToPosition()fue completamente ignorado, por una razón evidente.

¿Qué estaba usando?

recyclerView.scrollToPosition(items.size());

... ¿qué funcionó ?

recyclerView.scrollToPosition(items.size() - 1);
Roc Boronat
fuente
66
recyclerView.scrollToPosition(messages.size()-1);realmente funciona para mí, solo me pregunto la razón.
Motor Bai
25
Esto funcionó para mí. En realidad tiene sentido porque las listas Java están basadas en cero. Por ejemplo, la lista [0,1,2] tiene un tamaño de 3 pero la última posición es 2 porque comienza con 0.
Calvin
19
Para su información, para un desplazamiento suave, hay smoothScrollToPosition(...).
Ivan Morgillo
55
@Ivan Morgilo smoothScrollToPosition no funciona en absoluto: S
cesards
2
@IvanMorgillo: su respuesta parece funcionar mejor tanto con el resultado deseado como más suave = D. Parece que el desplazamiento a la posición es un poco defectuoso para mí (supongo que hay un pequeño retraso entre la función llamada y la creación de la vista, de todos modos no funciona correctamente)
Roee
54

Sé que es tarde para responder aquí, aún si alguien quiere saber la solución está por debajo

conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() - 1);
WritingForAnroid
fuente
8
Debe ser conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() **-1**);como las posiciones en las listas están basadas en cero.
Berťák
2
Esto no ayudará si la altura de la última fila es mayor que la altura de la pantalla.
Anuj Jindal
Esto es lo que necesitaba. Aunque tuve que envolverlo en un controlador posterior al retraso para que todo funcionara correctamente.
Marc Alexander
18

Para desplazarse hacia abajo desde cualquier posición en la vista de reciclador hacia abajo

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
fuente
15

Agregue este código después de enviar el mensaje y antes de recibir el mensaje del servidor

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

Cuando llama setAdapter, eso no establece y coloca inmediatamente los elementos en la pantalla (que requiere una sola pasada de diseño), por lo tanto, su scrollToPosition()llamada no tiene elementos reales a los que desplazarse cuando la llama.

En su lugar, debe registrar un ViewTreeObserver.OnGlobalLayoutListener (a través de addOnGlobalLayoutListner () de un ViewTreeObservercreado por conversationView.getViewTreeObserver()) que retrasa su scrollToPosition()hasta después del primer pase de diseño:

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
fuente
1
Gracias, pero esto no funciona. Detiene el desplazamiento al detener el funcionamiento del lanzamiento y, si lo arrastra, se desplaza de forma extrañamente rápida.
waylaidwanderer
Parece que no estás eliminando al oyente. Solo debe llamarse exactamente una vez (justo después de que los elementos se presentan por primera vez) y nada durante el desplazamiento.
ianhanniballake
OnGlobalLayoutListenerprobablemente no se elimine porque lo está buscando vto.isAlive(). No sé el motivo, pero a ViewTreeObserverveces se cambia por un View, así que en lugar de isAlivebuscarlo mejor, simplemente póngase al día ViewTreeObserverconconversationView.getViewTreeObserver().removeGlobalOnLayoutListener
Dmitry Zaytsev
@ianhanniballake He propuesto una edición para solucionar este problema.
Saket
6

Solución para Kotlin :

aplique el siguiente código después de configurar "recyclerView.adapter" o después de "recyclerView.adapter.notifyDataSetChanged ()"

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

En Kotlin:

recyclerView.viewTreeObserver.addOnGlobalLayoutListener { scrollToEnd() }

private fun scrollToEnd() =
        (adapter.itemCount - 1).takeIf { it > 0 }?.let(recyclerView::smoothScrollToPosition)
Yanchenko
fuente
Ja, gracias por GlobalLayoutListener! Después de refactorizar, tuve que usar su método para desplazarme. No olvide eliminar el oyente como en stackoverflow.com/questions/28264139/… , o no desplazará RecyclerView de regreso al principio.
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));
    }
  }
}

y

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

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

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

el código anterior funciona, pero no es suave ni genial.

Yuki Yoshida
fuente
4

SI tiene un adaptador conectado con recyclerView, simplemente haga esto.

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

Wasim Amin
fuente
4

Roc answer es de gran ayuda. Me gustaría agregarle un pequeño bloque:

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

En mi caso, donde las vistas no tienen la misma altura, llamar a scrollToPosition en LayoutManager funcionó para desplazarse realmente hacia la parte inferior y ver completamente el último elemento:

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

Esto funciona perfectamente bien para mí:

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

Desplácese por primera vez cuando ingrese en la vista de reciclador por primera vez y luego use

linearLayoutManager.scrollToPositionWithOffset(messageHashMap.size()-1

poner menos para desplazarse hacia abajo para desplazarse hacia arriba poner en valor positivo);

si la vista es muy grande en altura, entonces se usa desplazamiento a la posición de desplazamiento particular para la parte superior de la vista y luego se usa

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

Solo la respuesta de Ian fue capaz de hacer que mi RecyclerViewpergamino llegara a una posición específica. Sin embargo, The RecyclerViewno pudo desplazarse después cuando lo usé scrollToPosition(). smoothScrollToPosition()funcionó pero la animación inicial lo hizo demasiado lento cuando la lista era larga. La razón fue que el oyente no fue eliminado. Utilicé el siguiente código para eliminar la corriente ViewTreeObservery funcionó de maravilla.

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

este código te dará la última publicación primero, creo que esta respuesta es ú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
fuente
0

Probé un método de @galex , funcionó hasta refactorizar. Así que usé una respuesta de @yanchenko y cambié un poco. Probablemente esto se onCreateView()deba a que llamé a desplazamiento desde donde se creó una vista de fragmento (y probablemente no tenía el tamaño correcto).

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

También puede agregar una marca de me viewTreeObserver.isAlivegusta en https://stackoverflow.com/a/39001731/2914140 .

CoolMind
fuente
0

Si ninguno de estos funcionó,

deberías intentar usar:

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

Al hacer esto, está solicitando que se muestre un cierto RestraintLayout (o lo que sea que tenga). El pergamino es instantáneo.

Trabajo incluso con el teclado que se muestra.

Arnaud
fuente