RecyclerView прокручивает вверх на notifyDataSetChanged на экране чата
Я пытаюсь создать экран с сообщениями, используя recyclerView, который будет начинаться снизу и загружать дополнительные данные, когда пользователь достигнет верхней части чата. Но я сталкиваюсь с этой странной проблемой.
Мой recyclerView прокручивает вверх по вызову notifyDataSetChanged. Из-за этого onLoadMore вызывается несколько раз.
Вот мой код:
LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setOrientation(LinearLayoutManager.VERTICAL);
llm.setStackFromEnd(true);
recyclerView.setLayoutManager(llm);
** в адаптере
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (messages.size() > 8 && position == 0 && null != mLoadMoreCallbacks) {
mLoadMoreCallbacks.onLoadMore();
}
** в деятельности
@Override
public void onLoadMore() {
// Get data from database and add into arrayList
chatMessagesAdapter.notifyDataSetChanged();
}
Это просто, что recyclerView прокручивает вверх. Если прокрутка до верха остановится, эта проблема будет решена. Пожалуйста, помогите мне выяснить причину этой проблемы. Заранее спасибо.
9 ответов
Я думаю, что вы не должны использовать onBindViewHolder таким образом, удалить этот код, адаптер должен только связывать данные модели, а не прослушивать прокрутку.
Я обычно делаю "onLoadMore" следующим образом:
В Деятельности:
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;
}
Это прекрасно работает для меня, и "totallyLoaded" используется, если сервер не возвращает больше сообщений, чтобы прекратить совершать серверные вызовы. Надеюсь, это поможет вам.
Вы видите, это естественно для List
прокрутить до самого верхнего элемента при вставке новых элементов. Ну, вы идете в правильном направлении, но я думаю, что вы забыли добавить setReverseLayout(true)
,
Здесь setStackFromEnd(true)
просто говорит List
складывать элементы, начиная с нижней части представления, но при использовании в сочетании с setReverseLayout(true)
он перевернет порядок элементов и представлений, поэтому самый новый элемент всегда отображается внизу представления.
Ваш окончательный layoutManager будет выглядеть примерно так:
mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
mRecyclerView.setLayoutManager(mLayoutManager);
Это мой способ избежать перемещения прокрутки вверх вместо использования notifyDataSetChanged()
, Я использую notifyItemRangeChanged();
List<Object> tempList = new ArrayList<>();
tempList.addAll(mList);
mList.clear();
mList.addAll(tempList);
notifyItemRangeChanged(0, mList.size());
Обновление: по другой причине ваш другой вид вверху фокусируется, поэтому он будет переходить наверх, когда вы вызываете какие-либо уведомления, поэтому удалите все фокусы, добавив android:focusableInTouchMode="true"
в GroupView.
НЕ звоните notifyDataSetChanged()
на RecyclerView
, Используйте новые методы, такие как notifyItemChanged()
, notifyItemRangeChanged()
, notifyItemInserted()
и т.д... А если ты используешь notifyItemRangeInserted()
-
не звони setAdapter()
метод после этого..!
Я предлагаю вам использовать notifyItemRangeInserted
метод RecyclerView.Adapter
за LoadMore
операции. Вы добавляете набор новых элементов в свой список, чтобы вам не нужно было уведомлять весь набор данных.
notifyItemRangeInserted(int positionStart, int itemCount)
Уведомите всех зарегистрированных наблюдателей о том, что отраженные в данный момент элементы itemCount, начиная с positionStart, были недавно вставлены.
Для получения дополнительной информации: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html
Вам нужно благородно вещь в определенном диапазоне
@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());
}
Оформить заказ Firebase Friendlychat исходный код на Github.
Он ведет себя так, как вы хотите, особенно в:
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);
}
}
});
У вас есть эта проблема, потому что каждый раз, когда ваше состояние будет верным, вы звоните loadMore
метод даже loadMore
находился в рабочем состоянии, для решения этой проблемы вы должны добавить одно булево значение в ваш код и проверить это тоже.
проверьте мой следующий код, чтобы получить более четкое представление.
1 - объявите одно логическое значение в вашем классе адаптера
2- установите его в значение true в вашем состоянии
3- установите значение false после получения данных из базы данных и уведомления вашего адаптера.
поэтому ваш код должен выглядеть следующим образом:
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
}
в деятельности:
@Override
public void onLoadMore() {
// Get data from database and add into arrayList
chatMessagesAdapter.notifyDataSetChanged();
chatMessagesAdapter. setLoadingDataInProgress(false);
}
Это должно решить вашу проблему, но я предпочитаю справиться loadMore
внутри класса Activity или Presenter с установленным addOnScrollListener
на RecyclerView
и проверьте, если findFirstVisibleItemPosition
в LayoutManager
0, затем загрузить данные.
Я написал одну библиотеку для нумерации страниц, не стесняйтесь использовать или настраивать ее.
PS: как другой пользователь упоминал, не используйте notifyDataSetChanged
потому что это обновит все представления, включая видимые представления, которые вы не хотите обновлять, вместо этого используйте notifyItemRangeInsert
, в вашем случае вы должны уведомить от 0 до размера загружаемых данных из базы данных. В вашем случае, когда вы загружаете сверху, notifyDataSetChanged
изменит положение прокрутки на вершину новых загруженных данных, поэтому вы ДОЛЖНЫ использовать notifyItemRangeInsert
чтобы чувствовать себя хорошо в вашем приложении
Вам нужно указать предмет в определенном диапазоне, как показано ниже:
@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());
}
Я не полагаюсь на onBindViewHolder для такого рода вещей. Он может быть вызван несколько раз для позиции. Для списков, у которых есть опция load more, возможно, вы должны использовать что-то вроде этого после того, как ваше представление переработчика завышено.
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();
}
}
}
});
Надеюсь, поможет.