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 в моем случае это работало каждый раз:

  1. Сначала создайте экземпляр LinearSmoothScroller:
  LinearSmoothScroller smoothScroller=new LinearSmoothScroller(activity){
            @Override
            protected int getVerticalSnapPreference() {
                return LinearSmoothScroller.SNAP_TO_START;
            }
        };
  1. И затем, когда вы хотите прокрутить представление рециркулятора в любую позицию, сделайте это:
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.