web-dev-qa-db-fra.com

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 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?

61
Konrad Morawski

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!

92
DawnYu

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());
43
Kaerdan

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

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

Ceci stockera la position exacte du défilement à tout moment dans mScrollY. 

  1. Stockez cette variable dans votre bundle et restaurez-la en restauration d'état à une variable différente, nous l'appellerons mStateScrollY.

  2. 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.

20
A. Steenbergen

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 

5
Amit Singh Tomar

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.

  1. Assurez-vous de mettre à jour la vue de votre recycleur à l’aide de DiffUtil. En savoir plus à ce sujet ici: https://www.journaldev.com/20873/Android-recyclerview-diffutil
  2. Lors de la reprise de votre activité, ou au moment où vous souhaitez mettre à jour votre activité, chargez les données dans votre vue recyclée. En utilisant diffUtil, seules les mises à jour seront effectuées sur la vue recyclée tout en maintenant sa position.

J'espère que cela t'aides.

1
Moses Mwongela
 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.

0

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.

0
The Original Android

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)
0
Morten Holmgaard

Cela fonctionne pour moi à Kotlin.

  1. Créez l'adaptateur et transmettez vos données dans le constructeur
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. changer cette propriété et appeler notifyDataSetChanged ()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

Le décalage de défilement ne change pas.

0
Chris8447