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 прокручивается вверх. Если прокрутка к верхним стопам, эта проблема будет решена. Пожалуйста, помогите мне разобраться в причине этой проблемы. Спасибо заранее.
Ответы
Ответ 1
Вы видите, что естественно, чтобы List
прокручивался до самого верхнего элемента при вставке новых элементов. Ну, вы идете в правильном направлении, но я думаю, вы забыли добавить setReverseLayout(true)
.
Здесь setStackFromEnd(true)
просто сообщает List
для стека элементов, начиная с нижней части представления, но при использовании в комбинации с setReverseLayout(true)
он будет отменять порядок элементов и представлений, чтобы новый элемент всегда показывался внизу представления.
Ваш окончательный layoutManager будет выглядеть примерно так:
mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
mRecyclerView.setLayoutManager(mLayoutManager);
Ответ 2
Я думаю, что вы не должны использовать 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;
}
Это работает для меня, и "totalLoaded" используется, если сервер не возвращает больше сообщений, чтобы остановить вызовы сервера. Надеюсь, это поможет вам.
Ответ 3
Это мой способ избежать перемещения прокрутки вверх. Вместо использования notifyDataSetChanged() я использую notifyItemRangeChanged();
List<Object> tempList = new ArrayList<>();
tempList.addAll(mList);
mList.clear();
mList.addAll(tempList);
notifyItemRangeChanged(0, mList.size());
Обновление: по другой причине, Ваше другое представление в верхней части фокусируется, поэтому оно переходит наверх при вызове любых уведомлений, поэтому удалите все фокусы, добавив android: focusableInTouchMode = "true" в GroupView.
Ответ 4
Я не полагаюсь на onBindViewHolder для такого рода вещей. Его можно назвать несколько раз для позиции. Для списков, у которых есть больше возможностей, возможно, вы должны использовать что-то подобное после того, как ваш recyclerview завышен.
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();
}
}
}
});
Надеюсь, что это поможет.
Ответ 5
Я предлагаю вам использовать notifyItemRangeInserted
метод RecyclerView.Adapter
для операций LoadMore
. Вы добавляете набор новых элементов в свой список, поэтому вам не нужно уведомлять весь набор данных.
notifyItemRangeInserted(int positionStart, int itemCount)
Сообщите зарегистрированным наблюдателям, что в настоящее время отраженный элемент элементы, начинающиеся с positionStart, были вставлены снова.
Для получения дополнительной информации:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html
Ответ 6
НЕ ВЫБРАТЬ notifyDataSetChanged()
в RecyclerView
. Используйте новые методы, такие как notifyItemChanged()
, notifyItemRangeChanged()
, notifyItemInserted()
и т.д....
И если вы используете notifyItemRangeInserted()
-
не вызывать метод setAdapter()
после этого..!
Ответ 7
У вас есть эта проблема, потому что каждый раз, когда ваше условие истинно, вы вызываете метод 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 с помощью set addOnScrollListener
on RecyclerView
и проверять, есть ли findFirstVisibleItemPosition
в LayoutManager
0, а затем загрузить данные.
Я написал одну библиотеку для разбивки на страницы, не стесняйтесь использовать ее или настраивать ее.
PS: поскольку другие упомянутые пользователи не используют notifyDataSetChanged
, потому что это обновит все представления, включит видимые виды, которые вы не хотите обновлять, вместо этого используйте notifyItemRangeInsert
, в вашем случае вы должны уведомить от 0 до размер загруженных данных из базы данных.
В вашем случае, когда вы загружаете сверху, notifyDataSetChanged
изменит положение прокрутки вверху новых загруженных данных, поэтому вы ДОЛЖНЫ использовать notifyItemRangeInsert
, чтобы получить хорошее ощущение в своем приложении
Ответ 8
Вам нужно nofity элемент в определенном диапазоне, как показано ниже:
@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());
}
Ответ 9
Оформить исходный код 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);
}
}
});
Ответ 10
Вам нужно nofity элемент в определенном диапазоне
@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());
}