Jak przewinąć do dołu RecyclerView? scrollToPosition nie działa

161

Chciałbym przewinąć do dołu listy RecyclerView po załadowaniu działania.

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(...)zgłasza wyjątek dotyczący braku obsługi w RecyclerView i conversationView.scrollToPosition(...)wydaje się, że nic nie robi.

Po powyższym bloku kodu dodałem

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

co nie działa. W. Jest 30 elementów GENERIC_MESSAGE_LIST.

waylaidwanderer
źródło
Dzwonisz scrollToPositionnatychmiast po ustawieniu adaptera?
ianhanniballake
@ianhanniballake tak, zaraz potem conversationView.setAdapter(conversationViewAdapter);.
waylaidwanderer
4
Dzieje się tak prawdopodobnie dlatego, że wywołujesz +1 zamiast -1, więc piąty element byłby pozycją [4], ponieważ zaczyna się od 0. Wykonanie +1 dałoby ArrayOutOfBounds ... czy tam się nie zawiesił?
RCB
1
Dodaj po setadapter mRecyclerView.scrollToPosition (carsList.size () - 1);
Webserveis

Odpowiedzi:

194

Po prostu ustaw setStackFromEnd=truelub setReverseLayout=truetak, aby LLM układał elementy od końca.

Różnica między tymi dwoma polega na tym setStackFromEnd, że widok zostanie ustawiony tak, aby pokazywał ostatni element, a kierunek układu pozostanie taki sam. (Tak więc w poziomym widoku Recyklera od lewej do prawej ostatni element zostanie wyświetlony, a przewijanie w lewo pokaże wcześniejsze elementy)

Natomiast setReverseLayoutzmieni kolejność elementów dodawanych przez Adapter. Układ rozpocznie się od ostatniego elementu, który będzie znajdował się najbardziej na lewo w widoku LTR Recycler, a następnie przewijanie w prawo pokaże wcześniejsze elementy.

Próba:

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

Szczegółowe informacje można znaleźć w dokumentacji .

yigit
źródło
132
To może być przydatne, ale nie wydaje się odpowiadać na zadane pytanie.
józef
6
Jeśli ustawisz zarówno stackFromEnd = true, jak i setReverseLayout = true, to nie zadziała. Czy ktoś zna jakieś obejście?
Luccas Correa
10
W widoku czatu działa linearLayoutManager.setStackFromEnd (true).
Muneeb Mirza
7
To w ogóle nie rozwiązuje problemu.
Orbit
1
Działa tylko do pełnego widoku. Nie ma zamiaru przewijać, po prostu układać od dołu.
MiguelSlv
376

Patrzyłem na ten post, aby znaleźć odpowiedź, ale ... Myślę, że wszyscy w tym poście mieli ten sam scenariusz co ja: scrollToPosition()został całkowicie zignorowany z oczywistego powodu.

Czego używałem?

recyclerView.scrollToPosition(items.size());

... co DZIAŁAŁO ?

recyclerView.scrollToPosition(items.size() - 1);
Roc Boronat
źródło
6
recyclerView.scrollToPosition(messages.size()-1);naprawdę działa na mnie, po prostu zastanawiam się, dlaczego.
Silnik Bai
25
To zadziałało dla mnie. W rzeczywistości ma to sens, ponieważ listy Java są oparte na zerach. Np. Lista [0,1,2] ma rozmiar 3, ale ostatnia pozycja to 2, ponieważ zaczyna się od 0.
Calvin
19
FYI, dla płynnego przewijania, jest smoothScrollToPosition(...).
Ivan Morgillo,
5
@Ivan Morgilo smoothScrollToPosition w ogóle nie działa: S
cesards
2
@IvanMorgillo - Twoja odpowiedź wydaje się działać lepiej zarówno z pożądanym wynikiem, jak i bardziej płynną = D. Wygląda na to, przewijania do pozycji jest dla mnie nieco buggy (domyślam się, że istnieje małe opóźnienie między funkcją o nazwie i utworzenia widoku tak czy inaczej to nie działa poprawnie?)
Roee
54

Wiem, że jest późno na odpowiedź tutaj, ale jeśli ktoś chce wiedzieć, rozwiązanie jest poniżej

conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() - 1);
WritingForAnroid
źródło
8
Powinno być conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() **-1**);tak, jak pozycje na listach są od zera.
Berťák
2
To nie pomoże, jeśli wysokość ostatniego wiersza jest większa niż wysokość ekranu.
Anuj Jindal
To jest to, czego potrzebowałem. Chociaż musiałem zawinąć go w program obsługi po opóźnieniu, aby wszystko działało poprawnie.
Marc Alexander
18

Aby przewinąć z dowolnej pozycji w widoku recyklingu do dołu

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
źródło
15

Dodaj ten kod po wysłaniu wiadomości i przed odebraniem wiadomości z serwera

recyclerView.scrollToPosition(mChatList.size() - 1);
Shubham Agrawal
źródło
10

Kiedy dzwonisz setAdapter, nie powoduje to natychmiastowego układania i pozycjonowania elementów na ekranie (co wymaga pojedynczego przejścia układu), dlatego teżscrollToPosition() wywołanie nie ma żadnych elementów do przewinięcia, gdy je wywołujesz.

Zamiast tego powinieneś zarejestrować ViewTreeObserver.OnGlobalLayoutListener (przez addOnGlobalLayoutListner () z ViewTreeObserverutworzonego przez conversationView.getViewTreeObserver()), który opóźnia twój czas scrollToPosition()do pierwszego przebiegu układu:

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
źródło
1
Dzięki, ale to nie działa. Przerywa przewijanie, zatrzymując rzucanie od działania, a jeśli przeciągniesz, przewija się dziwnie szybko.
waylaidwanderer
Wygląda na to, że nie usuwasz słuchacza. Powinien być wywołany tylko raz (zaraz po pierwszym ułożeniu elementów) i wcale nie podczas przewijania.
ianhanniballake
OnGlobalLayoutListenerprawdopodobnie nie zostanie usunięty, ponieważ sprawdzasz vto.isAlive(). Nie znam powodu, ale ViewTreeObserverczasami jest zamieniany na a View, więc zamiast sprawdzać isAlive, lepiej po prostu pobierz aktualny ViewTreeObserverzconversationView.getViewTreeObserver().removeGlobalOnLayoutListener
Dmitry Zaytsev
@ianhanniballake Zaproponowałem edycję, aby naprawić ten problem.
Saket
6

Rozwiązanie dla Kotlin :

zastosuj poniższy kod po ustawieniu „recyclinglerView.adapter” lub po „reclerView.adapter.notifyDataSetChanged ()”

recyclerView.scrollToPosition(recyclerView.adapter.itemCount - 1)
AbhinayMe
źródło
5

W Kotlinie:

recyclerView.viewTreeObserver.addOnGlobalLayoutListener { scrollToEnd() }

private fun scrollToEnd() =
        (adapter.itemCount - 1).takeIf { it > 0 }?.let(recyclerView::smoothScrollToPosition)
yanchenko
źródło
Hah, dzięki za GlobalLayoutListener! Po refaktoryzacji musiałem użyć twojej metody do przewijania. Nie zapomnij usunąć nasłuchiwania, jak w stackoverflow.com/questions/28264139/… , albo nie przewiniesz RecyclerView z powrotem do początku.
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));
    }
  }
}

i

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

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

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

powyższy kod działa, ale nie jest płynny ani fajny.

Yuki Yoshida
źródło
4

JEŚLI masz dołączony adapter z recyklerem, po prostu zrób to.

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

Wasim Amin
źródło
4

Odpowiedź Roc jest bardzo pomocna. Chciałbym dodać do niego mały blok:

mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1);
abedfar
źródło
4

W moim przypadku, gdy widoki nie mają tej samej wysokości, wywołanie scrollToPosition w LayoutManager działało, aby naprawdę przewinąć do dołu i zobaczyć w pełni ostatni element:

recycler.getLayoutManager().scrollToPosition(adapter.getItemCount() - 1);
galex
źródło
2

U mnie to działa doskonale:

AdapterChart adapterChart = new AdapterChart(getContext(),messageList);
recyclerView.setAdapter(adapterChart);
recyclerView.scrollToPosition(recyclerView.getAdapter().getItemCount()-1);
amaresh Hiremani
źródło
2

Przewiń po raz pierwszy przy pierwszym wejściu do widoku recyklera, a następnie użyj

linearLayoutManager.scrollToPositionWithOffset(messageHashMap.size()-1

wstaw minus, aby przewinąć w dół, aby przewinąć w górę, wstaw wartość dodatnią);

jeśli widok ma bardzo dużą wysokość, wówczas położenie przewijania do pozycji, określone przesunięcie jest używane do widoku z góry

int overallXScroldl =chatMessageBinding.rvChat.computeVerticalScrollOffset();
chatMessageBinding.rvChat.smoothScrollBy(0, Math.abs(overallXScroldl));
Dishant Kawatra
źródło
0

Tylko odpowiedź Iana umożliwiła mi RecyclerViewprzewinięcie do określonej pozycji. Jednak RecyclerViewnie był w stanie przewijać później, kiedy użyłem scrollToPosition(). smoothScrollToPosition()działało, ale początkowa animacja działała zbyt wolno, gdy lista była długa. Powodem było to, że słuchacz nie został usunięty. Użyłem poniższego kodu, aby usunąć prąd ViewTreeObserveri zadziałało to jako urok.

    mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            mRecyclerView.scrollToPosition(mPosition);
            mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    });
Anky An
źródło
0

ten kod da ci najpierw najnowszy post, myślę, że ta odpowiedź jest pomocna.

    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
źródło
0

Wypróbowałem metodę @galex , działała do refaktoryzacji. Więc użyłem odpowiedzi @yanchenko i trochę się zmieniłem. Prawdopodobnie dzieje się tak dlatego, że wywołałem scrolling onCreateView(), skąd został zbudowany widok fragmentu (i prawdopodobnie nie miał odpowiedniego rozmiaru).

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

Możesz również dodać czek viewTreeObserver.isAlivepodobny do https://stackoverflow.com/a/39001731/2914140 .

CoolMind
źródło
0

Jeśli żaden z nich nie zadziałał,

powinieneś spróbować użyć:

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

Robiąc to, żądasz wyświetlenia określonego ConstraintLayout (lub cokolwiek innego). Zwój jest natychmiastowy.

Pracuję nawet z pokazaną klawiaturą.

Arnaud
źródło