web-dev-qa-db-ja.com

チャット画面のnotifyDataSetChangedでRecyclerViewが一番上にスクロールする

私は、recyclerViewを使用してメッセージの種類の画面を作成しようとしています。これは、ユーザーがチャットの最後に到達したときに下から開始し、より多くのデータを読み込みます。しかし、私はこの奇妙な問題に直面しています。

NotifyDataSetChangedを呼び出すと、recyclerViewが一番上にスクロールします。このため、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が一番上までスクロールするだけです。トップストップまでスクロールすると、この問題は解決されます。この問題の原因を解明するのを手伝ってください。前もって感謝します。

15
user1288005

ご覧のとおり、新しいアイテムを挿入するときにListが一番上のアイテムにスクロールするのは自然なことです。まあ、あなたは正しい方向に進んでいますが、あなたはsetReverseLayout(true)を追加するのを忘れたと思います。

ここでsetStackFromEnd(true)は、Listにビューの下部からアイテムをスタックするように指示しますが、setReverseLayout(true)と組み合わせて使用​​すると、アイテムとビューの順序が逆になるので、最新のアイテムは常にビューの下部に表示されます。

最終的なlayoutManagerは次のようになります。

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

そのように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
Borja

これは、ScrollViewが先頭に移動するのを避けるための私の方法です。notifyDataSetChanged()を使用する代わりに、notifyItemRangeChanged();を使用します

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

更新:別の理由で、上部の別のビューがフォーカスされているため、通知を呼び出すと上部にジャンプします。GroupViewにAndroid:focusableInTouchMode="true"を追加して、すべてのフォーカスを削除します。

1

RecyclerViewnotifyDataSetChanged()を呼び出さないでください。 notifyItemChanged()notifyItemRangeChanged()notifyItemInserted()などの新しいメソッドを使用します。また、notifyItemRangeInserted()--を使用する場合

その後はsetAdapter()メソッドを呼び出さないでください。

0
Shobhit

特定の範囲でアイテムに通知する必要があります

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

    }
0
Harsh Singhal

以下のような特定の範囲でアイテムに通知する必要があります。

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

        }
0

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

条件がtrueになるたびにloadMoreメソッドを呼び出すたびにloadMoreが実行状態であっても、この問題が発生します。この問題を解決するには、コードにブール値を1つ入れ、それも確認する必要があります。

より明確にするために、次のコードを確認してください。

1-アダプタークラスで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);
}

これで問題が解決するはずですが、loadMoreaddOnScrollListenerに設定してRecyclerViewをActivityまたはPresenterクラス内で処理し、findFirstVisibleItemPositionLayoutManagerは0で、データをロードします。

私は1つ作成しました ページネーション用のライブラリ 、それを自由に使用またはカスタマイズしてください。

PS:他のユーザーが言及したように、notifyDataSetChangedは使用しないでください。これにより、すべてのビューが更新され、表示したくないビューが更新されます。代わりにnotifyItemRangeInsertを使用してください。 0からデータベースからロードされたデータのサイズまで。上からロードする場合、notifyDataSetChangedはスクロール位置を新しくロードされたデータの最上部に変更するため、notifyItemRangeInsertを使用してアプリを快適に使用する必要があります

0

私はこのようなことを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();
                }
            }
        }
    });

それが役に立てば幸い。

0
letitthebee

notifyItemRangeInsertedオペレーションには、RecyclerView.AdapterLoadMoreメソッドを使用することをお勧めします。リストに新しいアイテムのセットを追加すると、データセット全体に通知する必要がなくなります。

notifyItemRangeInserted(int positionStart, int itemCount)

登録されたオブザーバに、positionStartで始まる現在反映されているitemCountアイテムが新しく挿入されたことを通知します。

詳細: https://developer.Android.com/reference/Android/support/v7/widget/RecyclerView.Adapter.html

0
savepopulation