Как прокрутить до конца RecyclerView? scrollToPosition не работает


161

Я хотел бы прокрутить до конца списка RecyclerView после загрузки действия.

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(...)выдает исключение о том, что он не поддерживается в RecyclerView, и conversationView.scrollToPosition(...), похоже , ничего не делает.

После вышеупомянутого блока кода я добавил

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

который не работает. Есть 30 элементов в GENERIC_MESSAGE_LIST.


Вы звоните scrollToPositionсразу после установки адаптера?
ianhanniballake

@ianhanniballake да, это сразу после conversationView.setAdapter(conversationViewAdapter);.
waylaidwanderer

4
Вероятно, это потому, что вы звоните +1 вместо -1, поэтому 5-й элемент будет позицией [4], потому что он начинается с 0. Выполнение +1 даст вам ArrayOutOfBounds ... разве там не произошел сбой?
RCB

1
Добавить после setadapter mRecyclerView.scrollToPosition (carsList.size () - 1);
Вебсервис

Ответы:


196

Просто установите setStackFromEnd=trueили setReverseLayout=trueтак, чтобы LLM размещал элементы с конца.

Разница между этими двумя заключается в том, что setStackFromEndв представлении будет отображаться последний элемент, направление макета останется прежним. (Таким образом, в горизонтальном представлении Recycler слева направо будет показан последний элемент, а при прокрутке влево будут отображены более ранние элементы)

Принимая во внимание, setReverseLayoutчто изменится порядок элементов, добавленных адаптером. Компоновка начнется с последнего элемента, который будет самым левым в представлении LTR Recycler, а затем при прокрутке вправо отобразятся более ранние элементы.

Образец:

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

Смотрите документацию для деталей.


133
Это может быть что-то полезное, но, похоже, оно не отвечает на заданный вопрос.
Джозеф

6
Если вы установите оба stackFromEnd = true и setReverseLayout = true, это не сработает. Кто-нибудь знает обходной путь?
Лукас Корреа

10
Для просмотра чата работает linearLayoutManager.setStackFromEnd (true).
Муниб Мирза

7
Это не решает вопрос вообще.
Орбита

1
Работает только до полного просмотра. Он не предназначен для прокрутки, просто складывается снизу.
MiguelSlv

379

Я искал этот пост, чтобы найти ответ, но ... я думаю, что все в этом посте столкнулись с тем же сценарием, что и я: scrollToPosition()был полностью проигнорирован по очевидной причине.

Что я использовал?

recyclerView.scrollToPosition(items.size());

... что РАБОТАЕТ ?

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

6
recyclerView.scrollToPosition(messages.size()-1);действительно работает для меня, мне просто интересно, причина.
Двигатель Бай

25
Это сработало для меня. Это действительно имеет смысл, потому что списки Java основаны на нуле. Например, список [0,1,2] имеет размер 3, но последняя позиция равна 2, потому что он начинается с 0.
Calvin

19
К вашему сведению, для плавной прокрутки, есть smoothScrollToPosition(...).
Иван Моргильо

5
@ Иван Моргило smoothScrollToPosition вообще не работает: S
открытки

2
@IvanMorgillo - кажется, ваш ответ работает лучше как с желаемым результатом, так и более плавно = D. кажется, что прокрутка до позиции немного ошибочна для меня (я предполагаю, что есть небольшая задержка между вызываемой функцией и созданием представления? в любом случае это не работает должным образом)
Роу

54

Я знаю, что уже поздно, чтобы ответить здесь, но если кто-то хочет знать решение ниже

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

8
Так должно быть, conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() **-1**);поскольку позиции в списках начинаются с нуля.
Берняк

2
Это не поможет, если высота последнего ряда больше, чем высота экрана.
Анудж Джиндал

Это то, что мне было нужно. Хотя мне пришлось обернуть его в пост-задержку обработчика, чтобы все работало правильно.
Марк Александр

18

Для прокрутки вниз из любой позиции в обзоре переработчика вниз

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

Добавьте этот код после отправки сообщения и до получения сообщения с сервера

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

10

Когда вы звоните setAdapter, это не сразу раскладывает и не позиционирует элементы на экране (что занимает один проход макета), следовательно, ваш scrollToPosition()звонок не имеет фактических элементов для прокрутки, когда вы его вызываете.

Вместо этого вы должны зарегистрировать ViewTreeObserver.OnGlobalLayoutListener (через addOnGlobalLayoutListner () от ViewTreeObserverсозданного conversationView.getViewTreeObserver()), который задерживает ваше scrollToPosition()до первого прохода макета:

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
Спасибо, но это не работает. Он прерывает прокрутку, останавливая бросание с работы, и если вы перетаскиваете, он прокручивается странно быстро.
waylaidwanderer

Похоже, вы не удаляете слушателя. Его следует вызывать только один раз (сразу после первого размещения элементов), а не во время прокрутки.
ianhanniballake

OnGlobalLayoutListenerвероятно, не удаляется, потому что вы проверяете vto.isAlive(). Я не знаю причину, но ViewTreeObserverиногда меняется для View, так что вместо проверки isAliveвам лучше просто получить ток ViewTreeObserverсconversationView.getViewTreeObserver().removeGlobalOnLayoutListener
Дмитрий Зайцев

@ianhanniballake Я предложил изменить, чтобы исправить эту проблему.
Сакет

7

Решение для Котлина :

применять приведенный ниже код после настройки «recyclerView.adapter» или после «recyclerView.adapter.notifyDataSetChanged ()»

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

5

В Котлине:

recyclerView.viewTreeObserver.addOnGlobalLayoutListener { scrollToEnd() }

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

Ха, спасибо за GlobalLayoutListener! После рефакторинга мне пришлось использовать ваш метод для прокрутки. Не забудьте удалить прослушиватель, как в stackoverflow.com/questions/28264139/… , иначе вы не прокрутите RecyclerView обратно к началу.
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));
    }
  }
}

и

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

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

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

код выше работает, но это не гладко и не круто.


4

Если у вас есть адаптер, подключенный к recyclerView, просто сделайте это.

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


4

Ответ Roc - большая помощь. Я хотел бы добавить небольшой блок к нему:

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

4

В моем случае, когда представления не имеют одинаковую высоту, вызов scrollToPosition в LayoutManager работал для того, чтобы действительно прокрутить до конца и полностью увидеть последний элемент:

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

3

Первая прокрутка при первом входе в режим рециркуляции, затем используйте

linearLayoutManager.scrollToPositionWithOffset(messageHashMap.size()-1

положить в минус для прокрутки вниз для прокрутки вверх (положительное значение);

если вид очень большой по высоте, то для верхней части вида используется конкретное смещение scrolltoposition, тогда вы используете

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

2

Это прекрасно работает для меня:

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

0

Только ответ Яна смог сделать мой RecyclerViewсвиток до указанной позиции. Тем не менее, я RecyclerViewне смог прокрутить потом, когда я использовал scrollToPosition(). smoothScrollToPosition()работал, но первоначальная анимация делала его слишком медленным, когда список был длинным. Причина была в том, что слушатель не был удален. Я использовал код ниже, чтобы удалить текущий, ViewTreeObserverи он работал как шарм.

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

0

этот код даст вам последнее сообщение первым, я думаю, что этот ответ полезен.

    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

Пробовал метод @galex , он работал до рефакторинга. Поэтому я использовал ответ @yanchenko и немного изменился. Вероятно, это потому, что я вызвал прокрутку из того места onCreateView(), где было построено представление фрагментов (и, вероятно, не имело правильного размера).

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

Вы также можете добавить проверку viewTreeObserver.isAliveкак в https://stackoverflow.com/a/39001731/2914140 .


0

Если ничего из этого не сработало,

Вы должны попытаться использовать:

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

Делая это, вы запрашиваете определенный ConstraintLayout (или все, что у вас есть) для отображения. Свиток мгновенный.

Я работаю даже с показанной клавиатурой.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.