简体   繁体   English

RecyclerView 在聊天屏幕中的 notifyDataSetChanged 上滚动到顶部

[英]RecyclerView scrolls to top on notifyDataSetChanged in chat screen

I am trying to create messaging kind of screen using recyclerView which will start from bottom and will loadMore data when user reached top end of chat.我正在尝试使用 recyclerView 创建消息类型的屏幕,该屏幕将从底部开始,并在用户到达聊天的顶端时加载更多数据。 But I am facing this weird issue.但我正面临这个奇怪的问题。

My recyclerView scrolls to top on calling notifyDataSetChanged.我的 recyclerView 在调用 notifyDataSetChanged 时滚动到顶部。 Due to this onLoadMore gets called multiple times.由于这个 onLoadMore 被多次调用。

Here is my code:这是我的代码:

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

** In Adapter ** 在适配器中

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

** In Activity ** 在活动中

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

It's just that recyclerView scrolls to top.只是 recyclerView 滚动到顶部。 If scrolling to top stops, this issue will be resolved.如果滚动到顶部停止,此问题将得到解决。 Please help me to figure out the cause of this issue.请帮我找出这个问题的原因。 Thanks in advance.提前致谢。

I think you shouldn't use onBindViewHolder that way, remove that code, the adapter should only bind model data, not listen scrolling.我认为您不应该那样使用 onBindViewHolder,删除该代码,适配器应该只绑定模型数据,而不是监听滚动。

I usually do the "onLoadMore" this way:我通常以这种方式执行“onLoadMore”:

In the Activity:在活动中:

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;
}

This works perfeclty for me, and the "totallyLoaded" is used if the server doesn't return more messages, to stop making server calls.这对我来说很完美,如果服务器没有返回更多消息,则使用“totallyLoaded”来停止进行服务器调用。 Hope it helps you.希望对你有帮助。

You see, it's natural for List to scroll to the most top item when you insert new Items.您看,当您插入新项目时, List很自然地滚动到最顶部的项目。 Well you going in the right direction but I think you forgot adding setReverseLayout(true) .好吧,您朝着正确的方向前进,但我认为您忘记添加setReverseLayout(true)

Here the setStackFromEnd(true) just tells List to stack items starting from bottom of the view but when used in combination with the setReverseLayout(true) it will reverse order of items and views so the newest item is always shown at the bottom of the view.这里setStackFromEnd(true)只是告诉List从视图底部开始堆叠项目,但是当与setReverseLayout(true)结合使用时,它将颠倒项目和视图的顺序,因此最新的项目始终显示在视图底部.

Your final layoutManager would seems something like this:你最终的 layoutManager 看起来像这样:

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

This is my way to avoid scrollview move to top Instead of using notifyDataSetChanged() , I use notifyItemRangeChanged();这是我避免滚动视图移到顶部的方法而不是使用notifyDataSetChanged() ,我使用notifyItemRangeChanged();

List<Object> tempList = new ArrayList<>();
tempList.addAll(mList);
mList.clear();
mList.addAll(tempList);
notifyItemRangeChanged(0, mList.size());

Update: For another reason, Your another view in the top is focusing so it will jump to top when you call any notifies, so remove all focuses by adding android:focusableInTouchMode="true" in the GroupView.更新:出于另一个原因,顶部的另一个视图正在聚焦,因此当您调用任何通知时它会跳到顶部,因此通过在 GroupView 中添加android:focusableInTouchMode="true" focusableInTouchMode android:focusableInTouchMode="true"来移除所有android:focusableInTouchMode="true"

I do not rely on onBindViewHolder for these kind of things.我不依赖 onBindViewHolder 来处理这些事情。 It can be called multiple times for a position.一个位置可以多次调用它。 For the lists which has load more option maybe you should use something like this after your recyclerview inflated.对于具有加载更多选项的列表,也许你应该在你的 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();
                }
            }
        }
    });

Hope it helps.希望能帮助到你。

I suggest you to use notifyItemRangeInserted method of RecyclerView.Adapter for LoadMore operations.我建议您使用RecyclerView.Adapter notifyItemRangeInserted方法进行LoadMore操作。 You add a set of new items to your list so you do not need to notify whole dataset.您将一组新项目添加到您的列表中,因此您无需通知整个数据集。

notifyItemRangeInserted(int positionStart, int itemCount)

Notify any registered observers that the currently reflected itemCount items starting at positionStart have been newly inserted.通知所有已注册的观察者,从 positionStart 开始的当前反映的 itemCount 项已被新插入。

For more information: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html更多信息: https : //developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html

DON'T call notifyDataSetChanged() on the RecyclerView .不要在RecyclerView上调用notifyDataSetChanged() Use the new methods like notifyItemChanged() , notifyItemRangeChanged() , notifyItemInserted() , etc... And if u use notifyItemRangeInserted() --使用诸如notifyItemChanged()notifyItemRangeChanged()notifyItemInserted()等新方法......如果你使用notifyItemRangeInserted() --

don't call setAdapter() method after that..!之后不要调用setAdapter()方法..!

You have this issue because every time your condition be true you call loadMore method even loadMore was in running state, for solving this issue you must put one boolean value in your code and check that too.您会遇到这个问题,因为每次您的条件为真时,您都会调用loadMore方法,即使loadMore处于运行状态,为了解决此问题,您必须在代码中放入一个布尔值并进行检查。

check my following code to get more clear.检查我的以下代码以更清楚。

1- declare one boolean value in your adapter class 1- 在您的适配器类中声明一个布尔值

2- set it to true in your condition 2-在您的条件下将其设置为true

3- set it to false after you've got data from database and notified your adapter. 3- 从数据库获取数据并通知适配器后,将其设置为 false。

so your code must be like as following code:所以你的代码必须像下面的代码:

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

}

in Activity :在活动中:

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

      chatMessagesAdapter. setLoadingDataInProgress(false);
}

This must fix your problem but I prefer to handle loadMore inside Activity or Presenter class with set addOnScrollListener on RecyclerView and check if findFirstVisibleItemPosition in LayoutManager is 0 then load data.这必须解决您的问题,但我更喜欢在 Activity 或 Presenter 类中处理loadMore并在RecyclerView上设置addOnScrollListener并检查LayoutManager findFirstVisibleItemPosition是否为 0 然后加载数据。

I've wrote one library for pagination , feel free to use or custom it.我写了一个用于分页的库,可以随意使用或自定义它。

PS: As other user mentioned don't use notifyDataSetChanged because this will refresh all view include visible views that you don't want to refresh those, instead use notifyItemRangeInsert , in your case you must notify from 0 to size of loaded data from database. PS:正如其他用户提到的,不要使用notifyDataSetChanged因为这将刷新所有视图,包括您不想刷新的可见视图,而是使用notifyItemRangeInsert ,在您的情况下,您必须从 0 通知数据库加载数据的大小。 In your case as you load from top, notifyDataSetChanged will change scroll position to top of new loaded data, so you MUST use notifyItemRangeInsert to get good feel in your app在您的情况下,当您从顶部加载时, notifyDataSetChanged会将滚动位置更改为新加载数据的顶部,因此您必须使用notifyItemRangeInsert以获得良好的应用notifyItemRangeInsert

You need to nofity the item in specific range like below:您需要在特定范围内通知项目,如下所示:

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

        }

Checkout Firebase Friendlychat source-code on Github .Github上查看 Firebase Friendlychat 源代码。

It behaves like you want, specially at:它的行为就像你想要的,特别是在:

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

You need to nofity the item in specific range您需要在特定范围内通知项目

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

    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 RecyclerView notifyDataSetChanged 滚动到顶部 position - RecyclerView notifyDataSetChanged scrolls to top position NotifyDataSetChanged刷新RecyclerView,但也会滚动。 - NotifyDataSetChanged refreshes RecyclerView but scrolls as well. RecyclerView 滚动到顶部本身 - RecyclerView scrolls to the top itself recyclerview在notifyDataSetChanged()之后滚动到顶部 - recyclerview scroll to top after notifyDataSetChanged() NestedScrollView 在 Recyclerview 调整大小时滚动到顶部 - NestedScrollView scrolls to top on Recyclerview resized 在调用notifydatasetchanged后,Recyclerview滚动到顶部 - Recyclerview scrolling to top after calling notifydatasetchanged 调用 notifyDataSetChanged() 时 RecyclerView 跳转到顶部 - RecyclerView jumps to top when notifyDataSetChanged() called 在nestedscrollview的recyclerview内部的recyclerview的notifyDataChanged上,外部recyclerview滚动到顶部 - On notifyDataChanged of recyclerview inside a recyclerview in a nestedscrollview the outer recyclerview scrolls to top 使用 ItemTouchHelper 拖动项目时 RecyclerView 滚动到顶部 - RecyclerView scrolls to top when dragging item with ItemTouchHelper 隐藏键盘后,RecyclerView滚动到顶部 - RecyclerView scrolls to top after keyboard hides
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM