it-swarm.com.de

RecyclerView - Blättern Sie zu einer Position, die nicht jedes Mal funktioniert

Ich habe eine horizontale scrollbare RecyclerView implementiert. Meine RecyclerView verwendet eine LinearLayoutManager, und das Problem, mit dem ich konfrontiert bin, ist, dass ich versuche, scrollToPosition(position) oder smoothScrollToPosition(position) oder von LinearLayoutManagers scrollToPositionWithOffset(position) zu verwenden. Weder funktioniert für mich. Entweder wird bei einem Bildlaufaufruf nicht an den gewünschten Ort geblättert oder die Variable OnScrollListener nicht aufgerufen.

Bisher habe ich so viele verschiedene Code-Kombinationen ausprobiert, dass ich sie nicht alle hier posten kann. Folgendes ist das, was für mich funktioniert (aber nur teilweise):

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

Ich entschied mich für scrollToPositionWithOffset() wegen this , aber der Fall ist möglicherweise anders, da ich einen LinearLayoutManager anstelle von GridLayoutManager verwende. Aber die Lösung funktioniert auch für mich, aber wie gesagt, nur teilweise.

  • Wenn der Bildlaufaufruf von der 0. Position bis zu totalSize erfolgt, funktioniert der Bildlauf wie ein Zauber.
  • Wenn der Bildlauf von totalSize - 7 bis totalSize - 3 erfolgt, scrolle ich beim ersten Mal nur zum siebten letzten Eintrag in der Liste. Beim zweiten Mal kann ich aber gut scrollen
  • Beim Scrollen von totalSize - 3 zu totalSize bekomme ich unerwartetes Verhalten. 

Wenn jemand eine Arbeit gefunden hat, würde ich es schätzen. Hier ist der Gist zu meinem benutzerdefinierten Code ReyclerView.

17
Abbas

Ich hatte vor einigen Wochen das gleiche Problem und fand nur eine wirklich schlechte Lösung, um es zu lösen. Musste eine postDelayed mit 200-300ms verwenden. 

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        yourList.scrollToPosition(position);
    }
}, 200);

Wenn Sie eine bessere Lösung gefunden haben, lassen Sie es mich wissen! Viel Glück!

31
Robert Banyai

Es stellte sich heraus, dass ich ein ähnliches Problem hatte, bis ich es verwendet habe

myRecyclerview.scrollToPosition(objectlist.size()-1)

Wenn Sie nur die Objektlistengröße eingeben, bleibt sie immer oben. Dies war, bis ich beschloss, die Größe einer Variablen gleichzusetzen. Auch das hat nicht funktioniert. Dann nahm ich an, dass es sich dabei um eine Out-Out -bound-Empfindung handelte, ohne mir etwas zu sagen. Also habe ich es um 1 abgezogen. Dann hat es funktioniert.

14

Nun, ich habe eine Arbeit gefunden.

Da scrollToPositionWithOffset(position, offset) der beste Anruf war, den ich hatte, verwende ich ihn. Ich habe die Ausgabe auf verschiedenen Datensätzen getestet und bisher keine Inkonsistenzen gefunden. Hoffentlich nicht (aber wenn jemand etwas findet, kommentieren Sie bitte unten).

Dann ist da noch die Frage von last 7 Items, dass der Scroller irgendwie nicht scrollen kann. Für diese unglücklichen Elemente verwende ich jetzt smoothScrollBy(dx, dy). Da es jedoch nur verwendet werden kann, wenn Sie die Position des scrollto-Elements kennen, wird es etwas komplizierter. Aber hier ist die Lösung (In der oben genannten Gist habe ich nur die folgende Funktionsdefinition geändert).

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

Hinweis: Ich verwende smoothScrollBy(dx, dy) nur für die letzten 7 Elemente. Auch Änderungen sind willkommen.

2
Abbas

Das Problem für mich war also, dass ich eine RecyclerView in einer NestedScrollView hatte. . Ich brauchte einige Zeit, um herauszufinden, dass dies das Problem war. Die Lösung hierfür ist ( Kotlin ):

val childY = recycler_view.y + recycler_view.getChildAt(position).y
nested_scrollview.smoothScrollTo(0, childY.toInt())

Java (Dank an Himagi https://stackoverflow.com/a/50367883/2917564 )

float y = recyclerView.getY() + recyclerView.getChildAt(selectedPosition).getY();    
scrollView.smoothScrollTo(0, (int) y);

Der Trick besteht darin, die verschachtelte Bildlaufansicht auf das Y anstatt auf die RecyclerView zu verschieben. Dies funktioniert anständig bei Android 5.0 Samsung J5 und Huawei P30 Pro mit Android 9.

2
jobbert

Keine der Methoden scheint für mich zu funktionieren. Nur die folgende Codezeile funktionierte

((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(adapter.currentPosition(),200);

Der zweite Parameter bezieht sich auf den Offset (eigentlich Abstand (in Pixel)) zwischen der Startkante der Elementansicht und der Startkante der RecyclerView. Ich habe es mit einem konstanten Wert geliefert, um die Top-Artikel auch sichtbar zu machen.

Weitere Referenz finden Sie über hier

2

Sie können LinearSmoothScroller verwenden, das hat in meinem Fall jedes Mal funktioniert:

  1. Erstellen Sie zuerst eine Instanz von LinearSmoothScroller:
  LinearSmoothScroller smoothScroller=new LinearSmoothScroller(activity){
            @Override
            protected int getVerticalSnapPreference() {
                return LinearSmoothScroller.SNAP_TO_START;
            }
        };
  1. Wenn Sie die Ansicht des Recyclers an eine beliebige Position verschieben möchten, gehen Sie wie folgt vor:
smoothScroller.setTargetPosition(pos);  // pos on which item you want to scroll recycler view
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);

Getan.

1
Suraj Vaishnav

Hatte das gleiche Problem. Mein Problem war, dass ich die Ansicht mit Daten in einer asynchronen Task nachgefüllt habe, nachdem ich versucht hatte zu scrollen. Von onPostExecute ofc wurde dieses Problem behoben. Eine Verzögerung hat dieses Problem ebenfalls behoben, da die Liste bei der Ausführung des Bildlaufs bereits wieder aufgefüllt wurde.

0
Jujinko

Die akzeptierte Antwort funktioniert, kann aber auch abbrechen. Der Hauptgrund für dieses Problem ist, dass die Recycler-Ansicht möglicherweise nicht bereit ist, wenn Sie zum Scrollen aufgefordert werden. Warten Sie am besten, bis die Recycler-Ansicht fertig ist, und scrollen Sie dann. Glücklicherweise hat Android eine solche Option zur Verfügung gestellt. Unten Lösung ist für Kotlin, können Sie die Java Alternative für das gleiche versuchen, es wird funktionieren.

newsRecyclerView.post {
    layoutManager?.scrollToPosition(viewModel.selectedItemPosition)
}

Die postrunnable-Methode ist für alle View-Elemente verfügbar und wird ausgeführt, sobald die View fertig ist. Dadurch wird sichergestellt, dass der Code genau dann ausgeführt wird, wenn dies erforderlich ist.

0
Harikrishnan

Das gleiche Problem hatte ich beim Erstellen eines zyklischen/kreisförmigen Adapters, bei dem ich nur nach unten scrollen konnte, jedoch nicht nach oben, da die Position auf 0 initialisiert wurde. Ich habe zuerst überlegt, Roberts Herangehensweise zu verwenden, aber es war zu unzuverlässig, da der Handler nur einmal geschossen hat. Wenn ich Pech hatte, konnte die Position in einigen Fällen nicht initialisiert werden.

Um dies zu beheben, erstelle ich ein Intervall Observable, in dem jede XXX-Zeit geprüft wird, ob die Initialisierung erfolgreich war und anschließend beseitigt wird. Dieser Ansatz hat für meinen Anwendungsfall sehr zuverlässig funktioniert.

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

Ich verwende die folgende Lösung, um das ausgewählte Element in der Recycler-Ansicht sichtbar zu machen, nachdem die Recycler-Ansicht neu geladen wurde (Ausrichtungsänderung usw.). Es überschreibt LinearLayoutManager und verwendet onSaveInstanceState , um die aktuelle Position des Recyclers zu speichern. Dann wird in onRestoreInstanceState die gespeicherte Position wiederhergestellt. Finaly, in onLayoutCompleted , scrollToPosition (mRecyclerPosition) wird verwendet, um die zuvor ausgewählte Position des Recyclers wieder sichtbar zu machen, aber wie Robert Banyai mitteilte, muss eine gewisse Verzögerung eingefügt werden, damit er zuverlässig funktioniert. Ich denke, es ist notwendig, genügend Zeit für den Adapter bereitzustellen, um die Daten zu laden, bevor scrollToPosition aufgerufen wird.

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);
    }
}
0
colens