web-dev-qa-db-fra.com

RecyclerView - Faites défiler jusqu'à la position ne fonctionnant pas à chaque fois

J'ai implémenté un RecyclerView scrollable horizontal. Ma RecyclerView utilise une LinearLayoutManager et le problème auquel je suis confronté est que lorsque j'essaie d'utiliser scrollToPosition(position) ou smoothScrollToPosition(position) ou de LinearLayoutManager's scrollToPositionWithOffset(position). Ni travaille pour moi. Soit un appel de défilement ne fait pas défiler jusqu’à l’emplacement souhaité, soit il n’appelle pas la variable OnScrollListener.

Jusqu'ici, j'ai essayé tellement de combinaisons de code différentes que je ne peux pas toutes les afficher ici. Voici celui qui fonctionne pour moi (mais seulement partiellement):

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

J'ai opté pour scrollToPositionWithOffset() en raison de this mais le cas est peut-être différent car j'utilise un LinearLayoutManager au lieu de GridLayoutManager. Mais la solution fonctionne aussi pour moi aussi, mais comme je l’ai dit plus tôt seulement partiellement.

  • Lorsque l'appel à faire défiler est de la 0ème position à totalSize - 7 scroll fonctionne comme un charme.
  • Lorsque vous faites défiler de totalSize - 7 à totalSize - 3, la première fois, je ne fais défiler que le 7ème dernier élément de la liste. La deuxième fois cependant je peux bien faire défiler
  • Lors du défilement de totalSize - 3 à totalSize, je commence à avoir un comportement inattendu. 

Si quelqu'un avait trouvé un travail autour de moi, je l'apprécierais. Voici le Gist à mon code de coutume ReyclerView.

17
Abbas

J'ai eu le même problème il y a quelques semaines et je n'ai trouvé qu'une très mauvaise solution pour le résoudre. Dû utiliser une postDelayed avec 200-300ms. 

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

Si vous avez trouvé une meilleure solution, s'il vous plaît faites le moi savoir! Bonne chance!

31
Robert Banyai

Il se trouve que j'avais un problème similaire jusqu'à ce que je l'utilise

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

Il resterait toujours au sommet en ne mettant que la taille de la liste d'objets. C'était jusqu'à ce que j'ai décidé de définir la taille égale à une variable. Encore une fois, cela n'a pas fonctionné. Ensuite, j'ai supposé que c'était peut-être en train de gérer une exception sortante, sans me le dire. Je l'ai donc soustraite de 1. Ensuite, cela a fonctionné.

14
Stefan Petit-Freres

Eh bien, j'ai trouvé un travail autour.

scrollToPositionWithOffset(position, offset) étant le meilleur appel que j'ai jamais eu, je l'utilise. J'ai testé la sortie sur différents ensembles de données et jusqu'à présent, je n'ai trouvé aucune incohérence. Espérons qu'il n'y en a pas (mais si quelqu'un en trouve, veuillez commenter ci-dessous).

Mais il y a ensuite la question de last 7 Items que le scroller est en quelque sorte incapable de faire défiler. Pour ces éléments malheureux, j'utilise maintenant smoothScrollBy(dx, dy). Mais comme il ne peut être utilisé que si vous connaissez la position de l'élément scrollto-item, cela devient un peu délicat. Mais voici la solution (Dans le Gist mentionné ci-dessus, j'ai changé uniquement la définition de fonction suivante).

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

Note: Je n'utilise que smoothScrollBy(dx, dy) pour les 7 derniers éléments. De plus, tout changement est le bienvenu.

2
Abbas

Aucune des méthodes ne semble fonctionner pour moi. Seule la ligne de code ci-dessous fonctionne

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

Le deuxième paramètre fait référence à offset, qui correspond en fait à la distance (en pixels) entre le bord de départ de la vue de l'article et le bord de départ de RecyclerView. Je l’ai fourni avec une valeur constante pour que les articles les plus importants soient également visibles.

Vérifiez plus de références sur ici

2
Jiju Induchoodan

Avait le même problème. Mon problème était que je remplissais la vue avec des données dans une tâche asynchrone après avoir essayé de faire défiler. De onPostExecute ofc résolu ce problème. Un délai a également corrigé ce problème car, lorsque le défilement était exécuté, la liste avait déjà été remplie.

0
Jujinko

J'ai eu le même problème lors de la création d'un adaptateur cyclique/circulaire, où je ne pouvais que faire défiler vers le bas mais pas vers le haut compte tenu de la position initialisée à 0. J'ai d'abord envisagé d'utiliser l'approche de Robert, mais c'était trop peu fiable car le Handler n'a été licencié qu'une seule fois. Si j'étais malchanceux, le poste ne serait pas initialisé dans certains cas.

Pour résoudre ce problème, je crée un intervalle Observable qui vérifie chaque XXX fois pour voir si l'initialisation a réussi et ensuite en disposer. Cette approche a très bien fonctionné pour mon cas d'utilisation.

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

J'utilise la solution ci-dessous pour rendre l'élément sélectionné dans la vue du recycleur visible après le rechargement de la vue du recycleur (changement d'orientation, etc.). Il annule LinearLayoutManager et utilise onSaveInstanceState pour enregistrer la position actuelle du recycleur. Ensuite, dans onRestoreInstanceState , la position enregistrée est restaurée. Enfin, dans onLayoutCompleted , scrollToPosition (mRecyclerPosition) est utilisé pour rendre à nouveau visible la position du recycleur précédemment sélectionnée, mais comme l'a indiqué Robert Banyai, un certain délai doit être inséré. Je suppose qu'il est nécessaire de laisser suffisamment de temps à l'adaptateur pour charger les données avant l'appel de 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);
    }
}
0
colens