web-dev-qa-db-fra.com

RecyclerView défile en haut de notifyDataSetChanged dans l'écran de discussion

J'essaie de créer un type d'écran de messagerie à l'aide de recyclerView, qui commence par le bas et charge plus de données lorsque l'utilisateur atteint le haut de la liste de discussion. Mais je suis confronté à ce problème étrange. 

Mon recyclerView défile vers le haut sur l'appel de notifyDataSetChanged. Pour cette raison, onLoadMore est appelé plusieurs fois.

Voici mon code:

LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setOrientation(LinearLayoutManager.VERTICAL);
llm.setStackFromEnd(true);
recyclerView.setLayoutManager(llm);

** dans l'adaptateur

 @Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (messages.size() > 8 && position == 0 && null != mLoadMoreCallbacks) {
        mLoadMoreCallbacks.onLoadMore();
    }

** En activité

@Override
public void onLoadMore() {
    // Get data from database and add into arrayList
      chatMessagesAdapter.notifyDataSetChanged();
}

C'est juste que recyclerView défile vers le haut. Si le défilement vers le haut s'arrête, ce problème sera résolu. Aidez-moi, s'il vous plaît, à trouver la cause de ce problème. Merci d'avance.

11
user1288005

Vous voyez, il est naturel que List défile jusqu'à l'élément le plus en haut lorsque vous insérez de nouveaux éléments. Eh bien, vous allez dans la bonne direction mais je pense que vous avez oublié d’ajoutersetReverseLayout(true)

Ici, setStackFromEnd(true) indique simplement à List d'empiler les éléments en commençant par le bas de la vue, mais lorsqu'il est utilisé en combinaison avec setReverseLayout(true), il inversera l'ordre des éléments et des vues de sorte que l'élément le plus récent soit toujours affiché en bas de la vue.

Votre mise en page finale semble ressembler à ceci:

        mLayoutManager = new LinearLayoutManager(getActivity());
        mLayoutManager.setReverseLayout(true);
        mLayoutManager.setStackFromEnd(true);
        mRecyclerView.setLayoutManager(mLayoutManager);
2
Keivan Esbati

Je pense que vous ne devriez pas utiliser onBindViewHolder de cette façon, supprimez ce code, l'adaptateur doit uniquement lier les données du modèle, pas écouter le défilement.

Je fais habituellement le "onLoadMore" de cette façon:

Dans l'activité:

private boolean isLoading, totallyLoaded; //
RecyclerView mMessages;
LinearLayoutManager manager;
ArrayList<Message> messagesArray;
MessagesAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //...
    mMessages.setHasFixedSize(true);
    manager = new LinearLayoutManager(this);
    manager.setStackFromEnd(true);
    mMessages.setLayoutManager(manager);
    mMessages.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            if (manager.findFirstVisibleItemPosition() == 0 && !isLoading && !totallyLoaded) {
                onLoadMore();
                isLoading = true;
            }
        }
    });
    messagesArray = new ArrayList<>();
    adapter = new MessagesAdapter(messagesArray, this);
    mMessages.setAdapter(adapter);
}


@Override
public void onLoadMore() {
    //get more messages...
    messagesArray.addAll(0, moreMessagesArray);
    adapter.notifyItemRangeInserted(0, (int) moreMessagesArray.size();
    isLoading = false;
}

Cela fonctionne parfaitement pour moi et le mot-clé "totallyLoaded" est utilisé si le serveur ne renvoie pas plus de messages pour arrêter les appels au serveur. J'espère que ça vous aide.

2
Borja

NE PAS appeler notifyDataSetChanged() sur la RecyclerView. Utilisez les nouvelles méthodes telles que notifyItemChanged(), notifyItemRangeChanged(), notifyItemInserted(), etc ... Et si vous utilisez notifyItemRangeInserted()--

n'appelle pas la méthode setAdapter() après cela ..!

0
Shobhit

Vous devez notifier l'article dans une plage spécifique 

   @Override
    public void onLoadMore() {
        // Get data from database and add into arrayList
        List<Messages> messegaes=getFromDB();
        chatMessagesAdapter.setMessageItemList(messages);
        // Notify adapter with appropriate notify methods
        int curSize = chatMessagesAdapter.getItemCount();
        chatMessagesAdapter.notifyItemRangeInserted(curSize,messages.size());

    }
0
Harsh Singhal

Vous devez notifier l'article dans une plage spécifique, comme ci-dessous:

       @Override
        public void onLoadMore() {
            // Get data from database and add into arrayList
            List<Messages> messegaes=getFromDB();
            chatMessagesAdapter.setMessageItemList(messages);
            // Notify adapter with appropriate notify methods
            int curSize = chatMessagesAdapter.getItemCount();
            chatMessagesAdapter.notifyItemRangeInserted(curSize,messages.size());

        }
0
Burhanuddin Rashid

Commander Le code source de Firebase Friendlychat sur Github .

Il se comporte comme vous le souhaitez, spécialement à:

    mFirebaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            super.onItemRangeInserted(positionStart, itemCount);
            int friendlyMessageCount = mFirebaseAdapter.getItemCount();
            int lastVisiblePosition = mLinearLayoutManager.findLastCompletelyVisibleItemPosition();
            // If the recycler view is initially being loaded or the user is at the bottom of the list, scroll
            // to the bottom of the list to show the newly added message.
            if (lastVisiblePosition == -1 ||
                    (positionStart >= (friendlyMessageCount - 1) && lastVisiblePosition == (positionStart - 1))) {
                mMessageRecyclerView.scrollToPosition(positionStart);
            }
        }
    });
0
gustavogbc

Vous avez ce problème car chaque fois que votre condition est vraie, vous appelez la méthode loadMore même si loadMore était en cours d'exécution. Pour résoudre ce problème, vous devez indiquer une valeur booléenne dans votre code et la vérifier également. 

vérifiez mon code suivant pour obtenir plus de clarté.

1- déclarer une valeur booléenne dans votre classe d'adaptateur

2- le mettre à vrai dans votre condition

3- définissez-le sur false après avoir obtenu les données de la base de données et notifié votre adaptateur.

donc votre code doit être comme le code suivant:

public class YourAdapter extend RecylerView.Adapter<.....> {

   private boolean loadingDataInProgress = false;


   public void setLoadingDataInProgress(boolean loadingDataInProgress) {
       this.loadingDataInProgress = loadingDataInProgress
   }


   .... 
   // other code


   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
      if (messages.size() > 8 && position == 0 && null != mLoadMoreCallbacks && !loadingDataInProgress){
         loadingDataInProgress = true;
         mLoadMoreCallbacks.onLoadMore();
    }


   ......
   //// other adapter code

}

en activité:

@Override
public void onLoadMore() {
      // Get data from database and add into arrayList
      chatMessagesAdapter.notifyDataSetChanged();

      chatMessagesAdapter. setLoadingDataInProgress(false);
}

Cela doit résoudre votre problème, mais je préfère gérer loadMore dans la classe Activity ou Presenter avec set addOnScrollListener sur RecyclerView et vérifier si findFirstVisibleItemPosition dans LayoutManager est 0, puis charger les données.

J'ai écrit un bibliothèque pour la pagination , n'hésitez pas à l'utiliser ou à le personnaliser.

PS: Comme les autres utilisateurs mentionnés, n'utilisez pas notifyDataSetChanged car cela actualisera toutes les vues, y compris les vues visibles que vous ne voulez pas actualiser, mais utilisez notifyItemRangeInsert. Dans votre cas, vous devez indiquer de 0 à la taille des données chargées dans la base de données. Dans votre cas, lorsque vous chargez du haut, notifyDataSetChanged changera la position de défilement en haut des nouvelles données chargées, vous DEVEZ donc utiliser notifyItemRangeInsert pour vous sentir bien dans votre application.

0
Shayan Pourvatan

Je ne compte pas sur onBindViewHolder pour ce genre de choses. On peut l'appeler plusieurs fois pour un poste. Pour les listes qui ont plus d’option de charge, vous devriez peut-être utiliser quelque chose comme ceci une fois que votre aperçu de recyclage est gonflé.

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if ((((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0)) {
                if (args.listModel.hasMore && null != mLoadMoreCallback && !loadMoreStarted) {
                    mLoadMoreCallbacks.onLoadMore();
                }
            }
        }
    });

J'espère que ça aide.

0
letitthebee

Je vous suggère d'utiliser la méthode notifyItemRangeInserted de RecyclerView.Adapter pour les opérations LoadMore. Vous ajoutez un ensemble de nouveaux éléments à votre liste afin que vous n'ayez pas besoin de notifier l'ensemble de données.

notifyItemRangeInserted(int positionStart, int itemCount)

Avertissez tous les observateurs inscrits que le nombre actuel d’éléments reflété les éléments commençant à positionStart ont été récemment insérés.

Pour plus d'informations: https://developer.Android.com/reference/Android/support/v7/widget/RecyclerView.Adapter.html

0
savepopulation