RecyclerView - прокрутка до позиции не работает каждый раз
Я выполнил горизонтальную прокручиваемую RecyclerView
. Мой RecyclerView
использует LinearLayoutManager
, и проблема, с которой я сталкиваюсь, заключается в том, что когда я пытаюсь использовать scrollToPosition(position)
или smoothScrollToPosition(position)
или из LinearLayoutManager
scrollToPositionWithOffset(position)
. Ни для меня не работает. Либо вызов прокрутки не прокручивается до нужного места, либо не вызывает OnScrollListener
.
До сих пор я пробовал так много разных комбинаций кода, что я не могу публиковать их здесь. Ниже приводится работа, которая работает для меня (но только частично):
public void smoothUserScrollTo(final int position) {
if (position < 0 || position > getAdapter().getItemCount()) {
Log.e(TAG, "An attempt to scroll out of adapter size has been stopped.");
return;
}
if (getLayoutManager() == null) {
Log.e(TAG, "Cannot scroll to position a LayoutManager is not set. " +
"Call setLayoutManager with a non-null layout.");
return;
}
if (getChildAdapterPosition(getCenterView()) == position) {
return;
}
stopScroll();
scrollToPosition(position);
if (lastScrollPosition == position) {
addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (left == oldLeft && right == oldRight && top == oldTop && bottom == oldBottom) {
removeOnLayoutChangeListener(this);
updateViews();
// removing the following line causes a position - 3 effect.
scrollToView(getChildAt(0));
}
}
});
}
lastScrollPosition = position;
}
@Override
public void scrollToPosition(int position) {
if (position < 0 || position > getAdapter().getItemCount()) {
Log.e(TAG, "An attempt to scroll out of adapter size has been stopped.");
return;
}
if (getLayoutManager() == null) {
Log.e(TAG, "Cannot scroll to position a LayoutManager is not set. " +
"Call setLayoutManager with a non-null layout.");
return;
}
// stopScroll();
((LinearLayoutManager) getLayoutManager()).scrollToPositionWithOffset(position, 0);
// getLayoutManager().scrollToPosition(position);
}
Я выбрал scrollToPositionWithOffset()
из-за этого, но дело может быть другим, поскольку я использую LinearLayoutManager вместо GridLayoutManager. Но решение действительно работает и для меня, но, как я сказал ранее, только частично.
- Когда вызов для прокрутки от 0-й позиции до totalSize-7 прокрутки работает как шарм.
- Когда свиток от totalSize - 7 до totalSize - 3, первый раз я прокручиваю только до 7-го последнего элемента в списке. Во второй раз, однако, я могу прокрутить отлично
- При прокрутке с totalSize - 3 на totalSize, я начинаю получать неожиданное поведение.
Если кто-нибудь нашел работу, я бы ее оценил. Здесь gist в мой код пользовательского ReyclerView
.
Ответы
Ответ 1
У меня была такая же проблема несколько недель назад, и я нашел только очень плохое решение для ее решения. Пришлось использовать postDelayed
с 200-300 мс.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
yourList.scrollToPosition(position);
}
}, 200);
Если вы нашли лучшее решение, пожалуйста, дайте мне знать! Удачи!
Ответ 2
Оказывается, у меня была аналогичная проблема, пока я не использовал
myRecyclerview.scrollToPosition(objectlist.size()-1)
Он всегда оставался наверху, только если бы он был помещен в размер списка объектов. Это было до тех пор, пока я не решил установить размер, равный переменной. Опять же, это не сработало. Тогда я предположил, что, возможно, это было обращение с нарушением обмана, не сказав мне. Поэтому я вычитал его на 1. Затем он работал.
Ответ 3
Ни один из методов, кажется, не работает для меня. Работала только нижняя строка кода
((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(adapter.currentPosition(),200);
Второй параметр относится к смещению, которое фактически является расстоянием (в пикселях) между начальным краем представления элемента и начальным краем RecyclerView. Я снабдил его постоянным значением, чтобы верхние элементы также были видны.
Проверьте для получения дополнительной ссылки здесь
Ответ 4
Ну, я нашел работу вокруг.
Так как scrollToPositionWithOffset(position, offset)
был лучшим звонком, я использовал его. Я тестировал вывод на разных наборах данных и до сих пор не обнаружил никаких несоответствий. Надеюсь, что нет (но если кто-нибудь найдет комментарий, пожалуйста, ниже).
Но тогда возникает вопрос о last 7 Items
, что скроллер каким-то образом не может прокручиваться. Для тех Несчастливых элементов я теперь использую smoothScrollBy(dx, dy)
. Но так как его можно использовать, только если вы знаете положение элемента scrollto-item, он становится немного сложным. Но вот решение (В приведенном выше gist я изменил только следующее определение функции).
public void smoothUserScrollTo(final int position) {
if (position < 0 || position > getAdapter().getItemCount()) {
Log.e(TAG, "An attempt to scroll out of adapter size has been stopped.");
return;
}
if (getLayoutManager() == null) {
Log.e(TAG, "Cannot scroll to position a LayoutManager is not set. " +
"Call setLayoutManager with a non-null layout.");
return;
}
if (getChildAdapterPosition(getCenterView()) == position) {
scrollToView(getCenterView());
return;
}
stopScroll();
scrollToPosition(position);
if (lastScrollPosition == position || position >= getAdapter().getItemCount() - 7) {
addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (left == oldLeft && right == oldRight && top == oldTop && bottom == oldBottom) {
if (getChildAdapterPosition(getCenterView()) == position) {
// Only remove the listener if/when the centered items is the item we want to scroll to.
removeOnLayoutChangeListener(this);
scrollToView(getCenterView());
return;
}
if (position >= 0 && position < getAdapter().getItemCount() - 7) {
removeOnLayoutChangeListener(this);
updateViews();
scrollToView(getChildAt(0));
}
else if (position >= getAdapter().getItemCount() - 7 && position <= getAdapter().getItemCount()){
// Search in the attached items of the RecyclerView for our required item.
// You can remove the loop and optimize your code further if you want to,
// but I am gonna leave it here as is. It is simple enough :-).
int childPosition = 0;
for (int i = 0; i < getChildCount(); i++) {
childPosition = getChildAdapterPosition(getChildAt(i));
if (childPosition == position) {
updateViews();
scrollToView(getChildAt(i));
break;
}
// Since we couldn't find the item in attached items.
// Scroll to the last attached item (It'll dettach and attach
// necessary items). So we can scroll once the view is stable again.
if (i == getChildCount() - 2) {
scrollToView(getChildAt(i));
}
}
}
}
}
});
}
lastScrollPosition = position;
}
Примечание. Я использую только smoothScrollBy(dx, dy)
для последних 7 элементов. Также любые изменения приветствуются.
Ответ 5
У меня была такая же проблема при создании циклического/кругового адаптера, где я мог прокручивать только вниз, но не вверх, учитывая, что позиция инициализируется до 0
. Сначала я подумал об использовании подхода Роберта, но это было слишком ненадежно, так как Хэндлер выстрелил только один раз, и если бы мне не повезло, в некоторых случаях позиция не инициализировалась.
Чтобы решить эту проблему, я создаю интервал Observable, который проверяет каждый XXX промежуток времени, чтобы увидеть, прошла ли инициализация успешно, а затем избавляется от нее. Этот подход работал очень надежно для моего варианта использования.
private fun initialisePositionToAllowBidirectionalScrolling(layoutManager: LinearLayoutManager, realItemCount: Int) {
val compositeDisposable = CompositeDisposable() // Added here for clarity, make this into a private global variable and clear in onDetach()/onPause() in case auto-disposal wouldn't ever occur here
val initPosition = realItemCount * 1000
Observable.interval(INIT_DELAY_MS, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({
if (layoutManager.findFirstVisibleItemPosition() == 0) {
layoutManager.scrollToPositionWithOffset(initPosition, 0)
if (layoutManager.findFirstCompletelyVisibleItemPosition() == initPosition) {
Timber.d("Adapter initialised, setting position to $initPosition and disposing interval subscription!")
compositeDisposable.clear()
}
}
}, {
Timber.e("Failed to initialise position!\n$it")
compositeDisposable.clear()
}).let { compositeDisposable.add(it) }
}
Ответ 6
Вы можете использовать LinearSmoothScroller
в моем случае это работало каждый раз:
- Сначала создайте экземпляр LinearSmoothScroller:
LinearSmoothScroller smoothScroller=new LinearSmoothScroller(activity){
@Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
};
- И затем, когда вы хотите прокрутить представление рециркулятора в любую позицию, сделайте это:
smoothScroller.setTargetPosition(pos); // pos on which item you want to scroll recycler view
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
Готово.
Ответ 7
Была та же проблема. Моя проблема заключалась в том, что я пополнил представление данными в async-задаче после того, как попытался прокрутить. Из onPostExecute ofc исправлена эта проблема. Задержка также исправила эту проблему, потому что, когда прокрутка выполнена, список уже был пополнен.
Ответ 8
Я использую ниже решение, чтобы сделать выбранный элемент в просмотре ресайзера видимым после перезагрузки ресайклера (изменение ориентации и т.д.). Он переопределяет LinearLayoutManager и использует onSaveInstanceState для сохранения текущей позиции ресайклинга. Затем в onRestoreInstanceState сохраняется сохраненная позиция. Окончательно, в onLayoutCompleted, scrollToPosition (mRecyclerPosition) используется, чтобы сделать ранее выбранную позицию ресайклера видимой снова, но, как заявил Роберт Баньяй, для того, чтобы она надежно работала, определенная задержка должна быть вставлен. Я предполагаю, что необходимо предоставить достаточное время для того, чтобы адаптер загружал данные до вызова scrollToPosition.
private class MyLayoutManager extends LinearLayoutManager{
private boolean isRestored;
public MyLayoutManager(Context context) {
super(context);
}
public MyLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public MyLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
if(isRestored && mRecyclerPosition >-1) {
Handler handler=new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
MyLayoutManager.this.scrollToPosition(mRecyclerPosition);
}
},200);
}
isRestored=false;
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable savedInstanceState = super.onSaveInstanceState();
Bundle bundle=new Bundle();
bundle.putParcelable("saved_state",savedInstanceState);
bundle.putInt("position", mRecyclerPosition);
return bundle;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
Parcelable savedState = ((Bundle)state).getParcelable("saved_state");
mRecyclerPosition = ((Bundle)state).getInt("position",-1);
isRestored=true;
super.onRestoreInstanceState(savedState);
}
}
Ответ 9
Принятый ответ будет работать, но он также может сломаться. Основная причина этой проблемы заключается в том, что представление перерабатывающего устройства может быть не готово к тому времени, когда вы попросите его прокрутить. Лучшее решение для того же - подождать, пока будет готово окно рециркуляции, а затем прокрутить. К счастью, Android предоставил один такой вариант. Ниже решение для Kotlin, вы можете попробовать альтернативу Java для того же, он будет работать.
newsRecyclerView.post {
layoutManager?.scrollToPosition(viewModel.selectedItemPosition)
}
Метод post-runnable доступен для всех элементов View и будет выполняться после того, как представление будет готово, что гарантирует выполнение кода именно тогда, когда это необходимо.
Ответ 10
Поэтому проблема для меня заключалась в том, что у меня был RecyclerView в NestedScrollView. Мне понадобилось время, чтобы понять, в чем проблема. Решение для этого (Котлин):
val childY = recycler_view.y + recycler_view.getChildAt(position).y
nested_scrollview.smoothScrollTo(0, childY.toInt())
Java (предоставлено Himagi fooobar.com/info/15694726/...)
float y = recyclerView.getY() + recyclerView.getChildAt(selectedPosition).getY();
scrollView.smoothScrollTo(0, (int) y);
Хитрость заключается в том, чтобы прокрутить вложенный вид прокрутки до Y вместо RecyclerView. Это работает на Android 5.0 Samsung J5 и Huawei P30 pro с Android 9.