web-dev-qa-db-fra.com

Comment enregistrer la position de défilement de RecyclerView à l'aide de RecyclerView.State?

J'ai une question à propos d'Android RecyclerView.State .

J'utilise un RecyclerView, comment puis-je l'utiliser et le lier avec RecyclerView.State?

Mon but est de enregistrer la position de défilement de RecyclerView.

85
陈文涛

Comment comptez-vous enregistrer la dernière position enregistrée avec RecyclerView.State?

Vous pouvez toujours compter sur le bon état de sauvegarde. Étendez RecyclerView et remplacez onSaveInstanceState () et onRestoreInstanceState():

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        LayoutManager layoutManager = getLayoutManager();
        if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
            mScrollPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
        }
        SavedState newState = new SavedState(superState);
        newState.mScrollPosition = mScrollPosition;
        return newState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        if(state != null && state instanceof SavedState){
            mScrollPosition = ((SavedState) state).mScrollPosition;
            LayoutManager layoutManager = getLayoutManager();
            if(layoutManager != null){
              int count = layoutManager.getChildCount();
              if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
                  layoutManager.scrollToPosition(mScrollPosition);
              }
            }
        }
    }

    static class SavedState extends Android.view.View.BaseSavedState {
        public int mScrollPosition;
        SavedState(Parcel in) {
            super(in);
            mScrollPosition = in.readInt();
        }
        SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(mScrollPosition);
        }
        public static final Parcelable.Creator<SavedState> CREATOR
                = new Parcelable.Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
32
Nikola Despotoski

Si vous utilisez LinearLayoutManager, il est livré avec une sauvegarde prédéfinie api linearLayoutManagerInstance.onSaveInstanceState () et restaurez api linearLayoutManagerInstance.onRestoreInstanceState (...)

Avec cela, vous pouvez enregistrer le colis retourné dans votre état externe. par exemple.,

outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());

et restaurez la position de restauration avec l’état que vous avez enregistré. par exemple,

Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);

Pour finir, votre code final ressemblera à quelque chose comme

private static final String BUNDLE_RECYCLER_LAYOUT = "classname.recycler.layout";

/**
 * This is a method for Fragment. 
 * You can do the same in onCreate or onRestoreInstanceState
 */
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);

    if(savedInstanceState != null)
    {
        Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
        recyclerView.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, recyclerView.getLayoutManager().onSaveInstanceState());
}

Edit: Vous pouvez également utiliser le même apis avec GridLayoutManager, car il s'agit d'une sous-classe de LinearLayoutManager. Merci @wegsehen pour la suggestion.

Edit: N'oubliez pas que si vous chargez également des données dans un thread en arrière-plan, vous devrez appeler onRestoreInstanceState dans votre méthode onPostExecute/onLoadFinished pour que la position soit restaurée lors du changement d'orientation, par exemple.

@Override
protected void onPostExecute(ArrayList<Movie> movies) {
    mLoadingIndicator.setVisibility(View.INVISIBLE);
    if (movies != null) {
        showMoviePosterDataView();
        mDataAdapter.setMovies(movies);
      mRecyclerView.getLayoutManager().onRestoreInstanceState(mSavedRecyclerLayoutState);
        } else {
            showErrorMessage();
        }
    }
181

Le magasin

lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();

Restaurer

((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);

et si cela ne fonctionne pas, essayez 

((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)

Mettez store dans onPause() Et restaurez-le dans onResume()

66
Gillis Haasnoot

I Définir les variables dans onCreate (), enregistrer la position de défilement dans onPause () et définir la position de défilement dans onResume ()

public static int index = -1;
public static int top = -1;
LinearLayoutManager mLayoutManager;

@Override
public void onCreate(Bundle savedInstanceState)
{
    //Set Variables
    super.onCreate(savedInstanceState);
    cRecyclerView = ( RecyclerView )findViewById(R.id.conv_recycler);
    mLayoutManager = new LinearLayoutManager(this);
    cRecyclerView.setHasFixedSize(true);
    cRecyclerView.setLayoutManager(mLayoutManager);

}

@Override
public void onPause()
{
    super.onPause();
    //read current recyclerview position
    index = mLayoutManager.findFirstVisibleItemPosition();
    View v = cRecyclerView.getChildAt(0);
    top = (v == null) ? 0 : (v.getTop() - cRecyclerView.getPaddingTop());
}

@Override
public void onResume()
{
    super.onResume();
    //set recyclerview position
    if(index != -1)
    {
        mLayoutManager.scrollToPositionWithOffset( index, top);
    }
}
16
farhad.kargaran

Je souhaitais enregistrer la position de défilement de Recycler View lors de la navigation en dehors de mon activité de liste, puis en cliquant sur le bouton Précédent pour revenir en arrière. La plupart des solutions apportées à ce problème étaient beaucoup plus compliquées que nécessaire ou ne fonctionnaient pas pour ma configuration. Je pensais donc partager ma solution.

Commencez par enregistrer votre état d'instance dans onPause, comme beaucoup l'ont montré. Il convient de souligner ici que cette version de onSaveInstanceState est une méthode de la classe RecyclerView.LayoutManager.

private LinearLayoutManager mLayoutManager;
Parcelable state;

        @Override
        public void onPause() {
            super.onPause();
            state = mLayoutManager.onSaveInstanceState();
        }

La clé pour que cela fonctionne correctement est de vous assurer que vous appelez onRestoreInstanceState après avoir connecté votre adaptateur, comme certains l'ont indiqué dans d'autres threads. Cependant, l’appel à la méthode est beaucoup plus simple que beaucoup ne l’ont indiqué.

private void someMethod() {
    mVenueRecyclerView.setAdapter(mVenueAdapter);
    mLayoutManager.onRestoreInstanceState(state);
}
13
Braden Holt

Vous n'avez plus besoin de sauvegarder et de restaurer l'état par vous-même. Si vous définissez un identifiant unique en xml et recyclerView.setSaveEnabled (true) (true par défaut), le système le fera automatiquement . En savoir plus sur ceci: http://trickyandroid.com/saving-Android-view- état-correctement/

6
AppiDevo

Voici comment je restaure la position de RecyclerView avec GridLayoutManager après une rotation lorsque vous devez recharger des données depuis Internet avec AsyncTaskLoader.

Créez une variable globale de Parcelable et GridLayoutManager et une chaîne finale statique:

private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";

Enregistrer l'état de gridLayoutManager dans onSaveInstance ()

 @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, 
            mGridLayoutManager.onSaveInstanceState());
        }

Restaurer dans onRestoreInstanceState

@Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        //restore recycler view at same position
        if (savedInstanceState != null) {
            savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
        }
    }

Ensuite, lorsque le chargeur récupère des données sur Internet, vous restaurez la position Recyclerview dans onLoadFinished ().

if(savedRecyclerLayoutState!=null){
                mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
            }

..de vous devez instancier gridLayoutManager dans onCreate .

3
Farmaker

Voici mon approche pour les sauvegarder et conserver l’état de recyclerview dans un fragment ..__ Tout d’abord, ajoutez les modifications de configuration de votre activité parent comme ci-dessous.

Android:configChanges="orientation|screenSize"

Ensuite, dans votre fragment, déclarez ces méthodes d'instance

private  Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;

Ajoutez le code ci-dessous dans votre méthode @onPause

@Override
public void onPause() {
    super.onPause();
    //This used to store the state of recycler view
    mBundleRecyclerViewState = new Bundle();
    mListState =mTrailersRecyclerView.getLayoutManager().onSaveInstanceState();
    mBundleRecyclerViewState.putParcelable(getResources().getString(R.string.recycler_scroll_position_key), mListState);
}

Ajoutez la méthode onConfigartionChanged comme ci-dessous.

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    //When orientation is changed then grid column count is also changed so get every time
    Log.e(TAG, "onConfigurationChanged: " );
    if (mBundleRecyclerViewState != null) {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                mListState = mBundleRecyclerViewState.getParcelable(getResources().getString(R.string.recycler_scroll_position_key));
                mTrailersRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);

            }
        }, 50);
    }
    mTrailersRecyclerView.setLayoutManager(mLinearLayoutManager);
}

Cette approche résoudra votre problème . Si vous avez un gestionnaire de disposition différent, remplacez simplement le gestionnaire de disposition supérieur.

2
Pawan kumar sharma

Tous les gestionnaires de disposition fournis dans la bibliothèque de support savent déjà comment enregistrer et restaurer la position de défilement.

La position de défilement est restaurée lors de la première passe de mise en page, ce qui se produit lorsque les conditions suivantes sont remplies:

  • un gestionnaire de disposition est attaché à la RecyclerView
  • un adpateur est attaché à la RecyclerView

En règle générale, vous définissez le gestionnaire de disposition en XML, il ne vous reste plus qu'à 

  1. Charger l'adaptateur avec des données
  2. Puis connectez l'adaptateur à la RecyclerView

Vous pouvez le faire à tout moment, il n’est pas contraint à Fragment.onCreateView ou Activity.onCreate.

Exemple: Assurez-vous que l'adaptateur est connecté à chaque fois que les données sont mises à jour. 

viewModel.liveData.observe(this) {
    // Load adapter.
    adapter.data = it

    if (list.adapter != adapter) {
        // Only set the adapter if we didn't do it already.
        list.adapter = adapter
    }
}

La position de défilement enregistrée est calculée à partir des données suivantes:

  • Position de l'adaptateur du premier article à l'écran
  • Décalage en pixels du haut de l'élément par rapport au haut de la liste (pour la disposition verticale)

Par conséquent, vous pouvez vous attendre à un léger décalage, par exemple. si vos éléments ont des dimensions différentes en orientation portrait et paysage.

1
Eugen Pechanec

Sur l'API Android Niveau 28, je m'assure simplement de configurer ma LinearLayoutManager et RecyclerView.Adapter dans ma méthode Fragment#onCreateView et tout ce que Just Worked ™. Je n'avais pas besoin de faire un travail onSaveInstanceState ou onRestoreInstanceState.

La réponse d'Eugen Pechanec explique pourquoi cela fonctionne.

0
Heath Borders

Pour moi, le problème était que je configurais un nouveau gestionnaire de mise en page chaque fois que je changeais d'adaptateur, perdant ainsi la position de défilement sur recyclerView.

0
Ronaldo Albertini

J'ai eu les mêmes exigences, mais les solutions présentées ici ne m'ont pas très bien compris, en raison de la source de données pour recyclerView.

J'extrais l'état LinearLayoutManager de RecyclerViews dans onPause ()

private Parcelable state;

Public void onPause() {
    state = mLinearLayoutManager.onSaveInstanceState();
}

Le colis state est enregistré dans onSaveInstanceState(Bundle outState) et extrait à nouveau dans onCreate(Bundle savedInstanceState), lorsque savedInstanceState != null.

Toutefois, l'adaptateur recyclerView est rempli et mis à jour par un objet ViewModel LiveData renvoyé par un appel DAO à une base de données Room.

mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
    @Override
    public void onChanged(@Nullable final List<String> displayStrings) {
        mAdapter.swapData(displayStrings);
        mLinearLayoutManager.onRestoreInstanceState(state);
    }
});

J'ai finalement trouvé que restaurer l'état de l'instance directement après la définition des données dans l'adaptateur garderait l'état de défilement sur les rotations.

Je suppose que cela soit dû au fait que l'état LinearLayoutManager que j'avais restauré était écrasé lorsque les données ont été renvoyées par l'appel de la base de données, ou que l'état restauré n'avait aucune signification par rapport à un LinearLayoutManager 'vide'.

Si les données de l'adaptateur sont disponibles directement (c'est-à-dire qu'elles ne sont pas actives sur un appel de base de données), la restauration de l'état de l'instance sur LinearLayoutManager peut être effectuée une fois l'adaptateur défini sur recyclerView.

La distinction entre les deux scénarios m'a retenu pendant des siècles.

0
MikeP

Je voudrais juste partager le récent problème que j'ai rencontré avec RecyclerView. J'espère que toute personne rencontrant le même problème en bénéficiera.

Exigences de mon projet: J'ai donc un RecyclerView qui répertorie certains éléments cliquables dans mon activité principale (Activité-A). Lorsque vous cliquez sur l'élément, une nouvelle activité s'affiche avec les détails de l'élément (activité-B).

J'ai implémenté dans le manifeste le fichier que l'activité-A est le parent de l'activité-B, de cette façon, j'ai un bouton de retour ou d'accueil sur la barre de tâches de l'activité-B

Problème: Chaque fois que j'ai appuyé sur le bouton Précédent ou Accueil dans la barre d’activité Activity-B, l’activité-A entre dans le cycle de vie complet de l’activité à partir de onCreate ()

Même si j'ai implémenté onSaveInstanceState () en enregistrant la liste de l'adaptateur de RecyclerView, lorsque Activity-A commence son cycle de vie, le saveInstanceState est toujours nul dans la méthode onCreate ().

En creusant davantage sur Internet, je suis tombé sur le même problème, mais la personne a remarqué que le bouton Précédent ou Accueil situé sous l'appareil Anroid (bouton Par défaut Précédent/Accueil), l'activité-A ne va pas dans le cycle de vie de l'activité.

Solution:

  1. J'ai enlevé l'étiquette pour l'activité parent dans le manifeste de l'activité-B
  2. J'ai activé le bouton d'accueil ou de retour sur l'activité B

    Sous la méthode onCreate (), ajoutez cette ligne supportActionBar? .SetDisplayHomeAsUpEnabled (true)

  3. Dans la méthode onOptionsItemSelected () trop amusante, j'ai vérifié l'élément.ItemId sur lequel l'élément est cliqué en fonction de l'ID. L'identifiant du bouton de retour est 

    Android.R.id.home

    Ensuite, implémentez un appel de fonction finish () dans Android.R.id.home

    Cela mettra fin à l'activité B et apportera Acitivy-A sans passer par le cycle de vie complet.

Pour mon besoin, c'est la meilleure solution jusqu'à présent.

     override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_show_project)

    supportActionBar?.title = projectName
    supportActionBar?.setDisplayHomeAsUpEnabled(true)

}


 override fun onOptionsItemSelected(item: MenuItem?): Boolean {

    when(item?.itemId){

        Android.R.id.home -> {
            finish()
        }
    }

    return super.onOptionsItemSelected(item)
}
0
Andy Parinas

J'ai eu le même problème avec une différence mineure, j'utilisais Databinding, et je configurais recyclerView adapter et le layoutManager dans les méthodes de liaison.

Le problème était que la liaison de données se produisait après onResume, donc elle réinitialisait mon layoutManager après onResume et cela signifiait qu'elle revenait à l'emplacement d'origine à chaque fois. Ainsi, lorsque j'ai arrêté de configurer layoutManager dans les méthodes de l'adaptateur Databinding, tout fonctionne à nouveau correctement.

0
Amin Keshavarzian