J'ai une question sur RecyclerView.State d'Android .
J'utilise un RecyclerView, comment puis-je l'utiliser et le lier à RecyclerView.State?
Mon but est de sauvegarder la position de défilement du RecyclerView .
J'ai une question sur RecyclerView.State d'Android .
J'utilise un RecyclerView, comment puis-je l'utiliser et le lier à RecyclerView.State?
Mon but est de sauvegarder la position de défilement du RecyclerView .
Réponses:
Comment comptez-vous enregistrer la dernière position enregistrée avec RecyclerView.State
?
Vous pouvez toujours compter sur un 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.getItemCount();
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];
}
};
}
À partir de la recyclerview:1.2.0-alpha02
version, StateRestorationPolicy
a été introduit. Cela pourrait être une meilleure approche du problème donné.
Ce sujet a été couvert dans l' article moyen des développeurs Android .
De plus, @ rubén-viguera a partagé plus de détails dans la réponse ci-dessous. https://stackoverflow.com/a/61609823/892500
Si vous utilisez LinearLayoutManager, il est livré avec une api de sauvegarde pré-construite linearLayoutManagerInstance.onSaveInstanceState () et une restauration de l'api linearLayoutManagerInstance.onRestoreInstanceState (...)
Avec cela, vous pouvez enregistrer le colis retourné dans votre état extérieur. 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 terminer, 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());
}
Modifier: vous pouvez également utiliser les mêmes API avec GridLayoutManager, car il s'agit d'une sous-classe de LinearLayoutManager. Merci @wegsehen pour la suggestion.
Edit: Souvenez-vous que si vous chargez également des données dans un thread d'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();
}
}
RecyclerView
mais pas si vous avez un ViewPager
avec un RecycerView
sur chaque page.
onCreateView
, mais après le chargement des données. Voir la réponse de @Farmaker ici.
Boutique
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Restaurer
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
et si cela ne fonctionne pas, essayez
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Mettre le magasin onPause()
et restaureronResume()
lastFirstVisiblePosition
après l' 0
avoir restauré. Ce cas est utile lorsque vous avez un fragment / activité qui charge différentes données dans un RecyclerView
, par exemple, une application d'exploration de fichiers.
SwipeRefreshLayout
?
RecyclerView
Je voulais enregistrer la position de défilement de Recycler View lors de la navigation loin de l'activité de ma liste, puis en cliquant sur le bouton de retour pour revenir en arrière. La plupart des solutions fournies pour ce problème étaient soit beaucoup plus compliquées que nécessaire ou ne fonctionnaient pas pour ma configuration, j'ai donc pensé partager ma solution.
Enregistrez d'abord l'état de votre instance dans onPause, comme beaucoup l'ont montré. Je pense qu'il vaut la peine 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 de méthode réel est beaucoup plus simple que beaucoup ne l'ont indiqué.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
I Définir des 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);
}
}
Vous n'avez plus besoin de sauvegarder et de restaurer l'état par vous-même. Si vous définissez un ID unique dans xml et recyclerView.setSaveEnabled (true) (true par défaut), le système le fera automatiquement. Voici plus à ce sujet: http://trickyandroid.com/saving-android-view-state-correctly/
Tous les gestionnaires de mise en page inclus 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:
RecyclerView
RecyclerView
En règle générale, vous définissez le gestionnaire de mise en page en XML. Il ne vous reste donc plus qu'à
RecyclerView
Vous pouvez le faire à tout moment, ce 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:
Par conséquent, vous pouvez vous attendre à une légère différence, par exemple si vos articles ont des dimensions différentes en orientation portrait et paysage.
Le RecyclerView
/ ScrollView
/ tout doit avoir un android:id
pour que son état soit sauvegardé.
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support
Voici mon approche pour les sauvegarder et maintenir l'état de recyclage dans un fragment. Tout d'abord, ajoutez des modifications de configuration dans votre activité parentale 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 mise en page différent, remplacez simplement le gestionnaire de mise en page supérieur.
C'est ainsi que je restaure la position de RecyclerView avec GridLayoutManager après la rotation lorsque vous avez besoin de 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 de recyclage dans onLoadFinished ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. Bien sûr, vous devez instancier gridLayoutManager dans onCreate. À votre santé
J'ai eu la même exigence, mais les solutions ici ne m'ont pas tout à fait permis de franchir la ligne, en raison de la source de données pour recyclerView.
J'extrayais l'état LinearLayoutManager de RecyclerViews dans onPause ()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
La parcelle state
est enregistrée onSaveInstanceState(Bundle outState)
et extraite à nouveau dans onCreate(Bundle savedInstanceState)
, quand savedInstanceState != null
.
Cependant, 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 la restauration de l'état de l'instance directement après la définition des données dans l'adaptateur conserverait l'état de défilement à travers les rotations.
Je soupçonne que cela est dû au fait que l' LinearLayoutManager
état que j'avais restauré était en cours d'écrasement lorsque les données ont été renvoyées par l'appel de base de données, ou que l'état restauré n'avait aucun sens par rapport à un LinearLayoutManager `` vide ''.
Si les données de l'adaptateur sont disponibles directement (c'est-à-dire non contigentes lors d'un appel de base de données), la restauration de l'état de l'instance sur le LinearLayoutManager peut être effectuée une fois l'adaptateur défini sur le recyclerView.
La distinction entre les deux scénarios m'a tenu debout pendant des siècles.
Sur l'API Android niveau 28, je m'assure simplement de configurer mon LinearLayoutManager
et RecyclerView.Adapter
dans ma Fragment#onCreateView
méthode, et tout Just Worked ™ ️. Je n'avais pas besoin d'en faire onSaveInstanceState
ou de onRestoreInstanceState
travailler.
La réponse d'Eugen Pechanec explique pourquoi cela fonctionne.
À partir de la version 1.2.0-alpha02 de la bibliothèque androidx recyclerView, il est désormais géré automatiquement. Ajoutez-le simplement avec:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
Et utilise:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
L'énumération StateRestorationPolicy a 3 options:
Notez qu'au moment de cette réponse, la bibliothèque recyclerView est toujours en alpha03, mais la phase alpha ne convient pas à des fins de production .
Pour moi, le problème était que je configurais un nouveau layoutmanager chaque fois que je changeais mon adaptateur, perdant la position de défilement sur recyclerView.
Je voudrais juste partager la récente situation difficile que je rencontre avec le RecyclerView. J'espère que toute personne confrontée au même problème en bénéficiera.
Exigence 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 fichier Manifest le fait que Activity-A est le parent de Activity-B, de cette façon, j'ai un bouton de retour ou d'accueil sur l'ActionBar de Activity-B
Problème: chaque fois que j'ai appuyé sur le bouton Retour ou Accueil dans la barre d'action Activity-B, Activity-A entre dans le cycle de vie complet de l'activité à partir de onCreate ()
Même si j'ai implémenté un onSaveInstanceState () enregistrant la liste de l'adaptateur de RecyclerView, lorsque Activity-A démarre son cycle de vie, le saveInstanceState est toujours nul dans la méthode onCreate ().
En approfondissant Internet, je suis tombé sur le même problème, mais la personne a remarqué que le bouton Retour ou Accueil sous l'appareil Anroid (bouton Retour / Accueil par défaut) n'entre pas dans le cycle de vie de l'activité.
Solution:
J'ai activé le bouton d'accueil ou de retour sur Activity-B
Sous la méthode onCreate (), ajoutez cette ligne supportActionBar? .SetDisplayHomeAsUpEnabled (true)
Dans la méthode onOptionsItemSelected (), j'ai vérifié l'élément item.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 le fichier android.R.id.home
Cela mettra fin à l'Activité-B et apportera Acitivy-A sans passer par tout le cycle de vie.
Pour mon besoin, c'est la meilleure solution à ce jour.
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)
}
J'ai eu le même problème avec une différence mineure, j'utilisais la liaison de données et je définissais l'adaptateur recyclerView 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 cela réinitialisait mon layoutManager après onResume et cela signifie qu'il retournait à son emplacement d'origine à chaque fois. Ainsi, lorsque j'ai arrêté de définir layoutManager dans les méthodes d'adaptateur de liaison de données, cela fonctionne à nouveau correctement.
Dans mon cas, je définissais le RecyclerView à la layoutManager
fois en XML et en onViewCreated
. La suppression de l'affectation a été onViewCreated
corrigée.
with(_binding.list) {
// layoutManager = LinearLayoutManager(context)
adapter = MyAdapter().apply {
listViewModel.data.observe(viewLifecycleOwner,
Observer {
it?.let { setItems(it) }
})
}
}
Activity.java:
public RecyclerView.LayoutManager mLayoutManager;
Parcelable state;
mLayoutManager = new LinearLayoutManager(this);
// Inside `onCreate()` lifecycle method, put the below code :
if(state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
@Override
protected void onResume() {
super.onResume();
if (state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
}
@Override
protected void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Pourquoi j'utilise OnSaveInstanceState()
dans les onPause()
moyens, alors que le passage à une autre activité onPause serait appelé.Il enregistrera cette position de défilement et restaurera la position lorsque nous revenons d'une autre activité.