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.
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);
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.
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 ..!
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());
}
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());
}
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);
}
}
});
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.
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.
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