【问题标题】:RecyclerView scrolls to top on notifyDataSetChanged in chat screenRecyclerView 在聊天屏幕中的 notifyDataSetChanged 上滚动到顶部
【发布时间】:2017-08-24 16:55:48
【问题描述】:

我正在尝试使用 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 滚动到顶部。如果滚动到顶部停止,此问题将得到解决。请帮我找出这个问题的原因。提前致谢。

【问题讨论】:

  • 你可以尝试添加 mLayoutManager.setReverseLayout(true);到你的布局管理器。
  • 再次使用通知设置您的适配器。可能对你有帮助
  • addonscrollListener 到你的 recyclerview 并找到你当前可见项目的正确位置然后调用 onLoadMore() 方法,并尝试从你的活动而不是适配器调用这个方法

标签: android android-recyclerview notifydatasetchanged linearlayoutmanager


【解决方案1】:

我认为您不应该那样使用 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”来停止进行服务器调用。希望对你有帮助。

【讨论】:

    【解决方案2】:

    您看,List 在您插入新项目时滚动到最顶部的项目是很自然的。好吧,您的方向是正确的,但我认为您忘记添加 setReverseLayout(true)

    这里setStackFromEnd(true) 只是告诉List 从视图底部开始堆叠项目,但是当与setReverseLayout(true) 结合使用时,它将反转项目和视图的顺序,因此最新的项目始终显示在底部视图。

    你最终的 layoutManager 应该是这样的:

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

    【讨论】:

      【解决方案3】:

      不要在RecyclerView 上拨打notifyDataSetChanged()。使用notifyItemChanged()notifyItemRangeChanged()notifyItemInserted()等新方法... 如果你使用notifyItemRangeInserted()--

      在那之后不要调用setAdapter() 方法..!

      【讨论】:

        【解决方案4】:

        这是我避免滚动视图移到顶部的方法 我没有使用notifyDataSetChanged(),而是使用notifyItemRangeChanged();

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

        更新: 由于另一个原因,您在顶部的另一个视图正在聚焦,因此当您调用任何通知时它会跳转到顶部,因此通过在 GroupView 中添加 android:focusableInTouchMode="true" 来删除所有焦点。

        【讨论】:

          【解决方案5】:

          我不依赖 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();
                          }
                      }
                  }
              });
          

          希望对你有帮助。

          【讨论】:

          • 我的 loadMore 功能运行良好。 Loadmore 函数被调用了很多次,因为我的 recyclerView 在 notifyDatasetChanged 之后滚动到顶部。如果它没有滚动到顶部,它应该可以正常工作。
          • 你的 recyclerview 有 wrap_content 属性吗?如果确实如此,则出于测量目的可能会发生这种情况。如果不是这样,也许使用 notifyItemChanged 或 notifyItemInserted 将是解决方案。顺便说一下,我上面的解决方案我也为普通(从顶部开始)列表调用 notifyDatasetChanged,但调用后它不会滚动到顶部。它在最后一个可见项目下广告更多项目。
          【解决方案6】:

          我建议您使用RecyclerView.AdapternotifyItemRangeInserted 方法进行LoadMore 操作。您将一组新项目添加到列表中,因此您无需通知整个数据集。

          notifyItemRangeInserted(int positionStart, int itemCount)
          

          通知所有注册的观察者当前反映的 itemCount 从 positionStart 开始的项目已被新插入。

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

          【讨论】:

          • 我已经尝试过这个,它可以按我的意愿工作,但有时它会给我这个错误:非致命异常:java.lang.IndexOutOfBoundsException:检测到不一致。在 android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4405) 在 android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView) 的无效项目位置 13(offset:13).state:21 .java:4363)
          • 我认为您发送的第一个参数 (positionStart) 错误,因此您收到此错误。如果在加载更多之前您的列表中有 10 个项目,并且您又加载了 10 个项目,那么您的 positionStart 为 10,itemCount 为 10。
          • 如果它对你有用,如果你接受我的回答并投票,我会很高兴。谢谢。
          【解决方案7】:

          您需要在特定范围内对项目进行分类,如下所示:

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

          【讨论】:

            【解决方案8】:

            Github 上查看 Firebase Friendlychat 源代码。

            它的行为如你所愿,特别是在:

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

            【讨论】:

              【解决方案9】:

              您遇到此问题是因为每次您的条件为真时,您都会调用 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);
              }
              

              这必须解决您的问题,但我更喜欢在 Activity 或 Presenter 类中处理 addOnScrollListener 并在 RecyclerView 上设置 addOnScrollListener 并检查 findFirstVisibleItemPosition 中的 findFirstVisibleItemPosition 是否为 0,然后加载数据。

              我写了一个library for pagination,随意使用或自定义。

              PS:正如其他用户提到的那样,不要使用notifyDataSetChanged,因为这将刷新所有视图,包括您不想刷新的可见视图,而是使用notifyItemRangeInsert,在您的情况下,您必须从 0 通知到从数据库加载数据的大小。 在您的情况下,当您从顶部加载时,notifyDataSetChanged 会将滚动位置更改为新加载数据的顶部,因此您必须使用 notifyItemRangeInsert 在您的应用中获得良好的感觉

              【讨论】:

                【解决方案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());
                
                    }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-02-10
                  • 1970-01-01
                  • 2010-11-16
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多