Actualiser les données dans RecyclerView et conserver sa position de défilement

Comment actualise-t-on les données affichées dans RecyclerView (en appelant notifyDataSetChanged sur son adaptateur) et assurez-vous que la position de défilement est réinitialisée exactement là où elle se trouvait?

En cas de bon getChildAt(0) ListView il suffit de récupérer getChildAt(0) , de vérifier son getTop() et d’appeler ensuite setSelectionFromTop avec les mêmes données exactes.

Cela ne semble pas possible dans le cas de RecyclerView .

Je suppose que je suis supposé utiliser son LayoutManager qui fournit effectivement scrollToPositionWithOffset(int position, int offset) , mais quelle est la bonne façon de récupérer la position et le décalage?

layoutManager.findFirstVisibleItemPosition() et layoutManager.getChildAt(0).getTop() ?

Ou existe-t-il une manière plus élégante de faire le travail?

J’utilise celui-ci.

 // Save state private Parcelable recyclerViewState; recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState(); // Restore state recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState); 

C’est plus simple, j’espère que cela vous aidera!

J’ai un problème assez similaire Et je suis venu avec la solution suivante.

Utiliser notifyDataSetChanged est une mauvaise idée. Vous devriez être plus précis, alors RecyclerView enregistrera l’état de défilement pour vous.

Par exemple, si vous avez seulement besoin de rafraîchir ou, en d’autres termes, de réorganiser chaque vue, procédez comme suit:

 adapter.notifyItemRangeChanged(0, adapter.getItemCount()); 

EDIT: Pour restaurer exactement la même position apparente , comme dans, faites en sorte qu’il ressemble exactement à cela, nous devons faire quelque chose de différent (voir ci-dessous comment restaurer la valeur de scrolly exacte):

Enregistrer la position et décaler comme ceci:

 LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager(); int firstItem = manager.findFirstVisibleItemPosition(); View firstItemView = manager.findViewByPosition(firstItem); float topOffset = firstItemView.getTop(); outState.putInt(ARGS_SCROLL_POS, firstItem); outState.putFloat(ARGS_SCROLL_OFFSET, topOffset); 

Et puis restaurez le parchemin comme ceci:

 LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager(); manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset); 

Cela restaure la liste à sa position apparente exacte. Apparent car il sera identique à l’utilisateur, mais il n’aura pas la même valeur de défilement (à cause des différences possibles dans les dimensions de la mise en page paysage / portrait).

Notez que cela ne fonctionne qu’avec LinearLayoutManager.

Ci-dessous comment restaurer le scrolly exact, ce qui rendra probablement la liste différente

  1. Appliquez un OnScrollListener comme ceci:

     private int mScrollY; private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); mScrollY += dy; } }; 

Cela stockera la position de défilement exacte à tout moment dans mScrollY.

  1. Stockez cette variable dans votre ensemble et restaurez-la dans une restauration d’état dans une variable différente , nous l’appellerons mStateScrollY.

  2. Après la restauration de l’état et après que votre RecyclerView a réinitialisé toutes ses données, réinitialisez le défilement avec ceci:

     mRecyclerView.scrollBy(0, mStateScrollY); 

C’est tout.

Attention, vous restaurez le parchemin dans une variable différente, ce qui est important, car OnScrollListener sera appelé avec .scrollBy () et définira mScrollY sur la valeur stockée dans mStateScrollY. Si vous ne le faites pas, mScrollY aura le double de la valeur de défilement (parce que OnScrollListener fonctionne avec des deltas, pas des défilement absolus).

Les économies d’État dans les activités peuvent être réalisées comme ceci:

 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(ARGS_SCROLL_Y, mScrollY); } 

Et pour restaurer, appelez ceci dans votre onCreate ():

 if(savedState != null){ mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0); } 

La sauvegarde de l’état dans des fragments fonctionne de la même manière, mais la sauvegarde de l’état nécessite un peu de travail supplémentaire, mais il y a beaucoup d’articles à ce sujet, vous ne devriez donc pas avoir de problème pour savoir comment et le restaurer rest le même.

Oui, vous pouvez résoudre ce problème en rendant le constructeur de l’adaptateur une seule fois, j’explique la partie codage ici: –

 if (appointmentListAdapter == null) { appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this); appointmentListAdapter.addAppointmentListData(appointmentList); appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener); appointmentRecyclerView.setAdapter(appointmentListAdapter); } else { appointmentListAdapter.addAppointmentListData(appointmentList); appointmentListAdapter.notifyDataSetChanged(); } 

Maintenant, vous pouvez voir que j’ai vérifié que l’adaptateur est nul ou non et qu’il s’initialise uniquement lorsqu’il est nul.

Si l’adaptateur n’est pas nul, je suis assuré que j’ai initialisé mon adaptateur au moins une fois.

Je vais donc simplement append la liste à l’adaptateur et appeler notifydatasetchanged.

RecyclerView conserve toujours la dernière position défilée. Par conséquent, vous n’avez pas à stocker la dernière position, il suffit d’appeler notifydatasetchanged, la vue recycleur actualise toujours les données sans les dépasser.

Merci Heureux Codage

Je n’ai pas utilisé Recyclerview mais je l’ai fait sur ListView. Exemple de code dans Recyclerview:

 setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { rowPos = mLayoutManager.findFirstVisibleItemPosition(); 

C’est l’auditeur lorsque l’utilisateur fait défiler. La surcharge de performance n’est pas significative. Et la première position visible est précise de cette façon.

Peut-être que c’est trop simple, mais je fais cela très simplement en appelant

 recreate(); 

La position d’affichage de mon recycleur est enregistrée.

Il suffit de retourner si l’ancienne position et la position sont les mêmes;

 private int oldPosition = -1; public void notifyItemSetChanged(int position, boolean hasDownloaded) { if (oldPosition == position) { return; } oldPosition = position; RLog.d(TAG, " notifyItemSetChanged :: " + position); DBMessageModel m = mMessages.get(position); m.setVideoHasDownloaded(hasDownloaded); notifyItemChanged(position, m); } 

J’ai eu ce problème avec une liste d’éléments qui avaient chacun un temps en minutes jusqu’à ce qu’ils soient «dus» et nécessitaient une mise à jour. Je mettrais à jour les données et ensuite, appelez

 orderAdapter.notifyDataSetChanged(); 

et il défilerait au sumt à chaque fois. Je l’ai remplacé par

  for(int i = 0; i < orderArrayList.size(); i++){ orderAdapter.notifyItemChanged(i); } 

et c'était bien. Aucune des autres méthodes de ce fil n'a fonctionné pour moi. Cependant, en utilisant cette méthode, chaque élément a été mis à jour lorsqu'il a été mis à jour. J'ai donc dû le mettre dans le fichier onCreateView du fragment parent.

 RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator(); if (animator instanceof SimpleItemAnimator) { ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); }