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

    }