Comment actualise-t-on les données affichées dans RecyclerView
(en appelant notifyDataSetChanged
sur son adaptateur) et s’assure que la position de défilement est réinitialisée exactement là où elle se trouvait?
En cas de bon vieux ListView
, il suffit de récupérer getChildAt(0)
, de vérifier son getTop()
et d'appeler setSelectionFromTop
avec les mêmes données exactes par la suite.
Cela ne semble pas être possible dans le cas de RecyclerView
.
Je suppose que je suis censé utiliser sa LayoutManager
qui fournit en effet 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 y a-t-il un moyen plus élégant 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, espérons 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
sauvegardera l’état de défilement pour vous.
Par exemple, si vous avez seulement besoin d'actualiser, ou en d'autres termes, vous souhaitez que chaque vue soit reliée, procédez comme suit:
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
EDIT: Pour restaurer exactement la même position apparente, vous devez faire quelque chose de légèrement différent (voir ci-dessous comment restaurer la valeur scrollY exacte):
Enregistrez la position et le décalage 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);
Ceci restaure la liste à sa position exacte apparente. Apparente, car elle aura le même aspect pour l’utilisateur, mais elle n’aura pas la même valeur scrollY (en raison des différences possibles dans les dimensions de la disposition paysage/portrait).
Notez que cela ne fonctionne qu'avec LinearLayoutManager.
--- Ci-dessous, comment restaurer l'exact scrollY, ce qui donnera probablement un aspect différent à la liste ---
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;
}
};
Ceci stockera la position exacte du défilement à tout moment dans mScrollY.
Stockez cette variable dans votre bundle et restaurez-la en restauration d'état à une variable différente, nous l'appellerons mStateScrollY.
Après la restauration de l'état et après, votre RecyclerView a réinitialisé toutes ses données, réinitialisez le défilement avec ceci:
mRecyclerView.scrollBy(0, mStateScrollY);
C'est tout.
Attention, si vous restaurez le défilement sur une variable différente, cela est important, car OnScrollListener sera appelé avec .scrollBy (), puis 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 (car OnScrollListener fonctionne avec des deltas et non des défilement absolus).
Les économies d’activité dans les activités peuvent être réalisées comme suit:
@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 d'état en fragments fonctionne de la même manière, mais elle nécessite un peu plus de travail, mais de nombreux articles traitent de cela. Vous ne devriez donc pas avoir de difficulté à savoir comment, les principes de sauvegarde du défilement. et le restaurer reste 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 suivante:
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é si l'adaptateur est nul ou non et ne l'initialiser que s'il est nul.
Si l'adaptateur n'est pas nul, je suis assuré d'avoir initialisé mon adaptateur au moins une fois.
Je vais donc simplement ajouter une liste à l'adaptateur et appeler notifydatasetchanged.
RecyclerView maintient 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 du recycleur actualise toujours les données sans aller au sommet.
Merci Bon codage
La réponse supérieure de @DawnYu fonctionne, mais la vue recyclée défilera d'abord vers le haut, puis reviendra à la position de défilement prévue, provoquant une réaction de type "scintillement" qui n'est pas agréable.
Pour actualiser le recyclerView, surtout après avoir quitté une autre activité, sans scintillement et en maintenant la position de défilement, vous devez procéder comme suit.
J'espère que cela t'aides.
mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
}
});
La solution ici est de continuer à faire défiler recyclage quand un nouveau message arrive.
La méthode onChanged () détecte l'action effectuée sur recyclerview.
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'écouteur lorsque l'utilisateur fait défiler. Le surcoût lié aux performances n’est pas significatif. Et la première position visible est précise de cette façon.
Gardez la position de défilement en utilisant @DawnYu answer pour envelopper notifyDataSetChanged()
comme ceci:
val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
adapter.notifyDataSetChanged()
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
Cela fonctionne pour moi à Kotlin.
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>() { ... }
adapter.currentPole = pole
adapter.notifyDataSetChanged()
Le décalage de défilement ne change pas.