私は、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が一番上までスクロールするだけです。トップストップまでスクロールすると、この問題は解決されます。この問題の原因を解明するのを手伝ってください。前もって感謝します。
ご覧のとおり、新しいアイテムを挿入するときにList
が一番上のアイテムにスクロールするのは自然なことです。まあ、あなたは正しい方向に進んでいますが、あなたはsetReverseLayout(true)
を追加するのを忘れたと思います。
ここでsetStackFromEnd(true)
は、List
にビューの下部からアイテムをスタックするように指示しますが、setReverseLayout(true)
と組み合わせて使用すると、アイテムとビューの順序が逆になるので、最新のアイテムは常にビューの下部に表示されます。
最終的なlayoutManagerは次のようになります。
mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
mRecyclerView.setLayoutManager(mLayoutManager);
そのように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」を使用してサーバー呼び出しを停止します。お役に立てば幸いです。
これは、ScrollViewが先頭に移動するのを避けるための私の方法です。notifyDataSetChanged()
を使用する代わりに、notifyItemRangeChanged();
を使用します
List<Object> tempList = new ArrayList<>();
tempList.addAll(mList);
mList.clear();
mList.addAll(tempList);
notifyItemRangeChanged(0, mList.size());
更新:別の理由で、上部の別のビューがフォーカスされているため、通知を呼び出すと上部にジャンプします。GroupViewにAndroid:focusableInTouchMode="true"
を追加して、すべてのフォーカスを削除します。
RecyclerView
でnotifyDataSetChanged()
を呼び出さないでください。 notifyItemChanged()
、notifyItemRangeChanged()
、notifyItemInserted()
などの新しいメソッドを使用します。また、notifyItemRangeInserted()
--を使用する場合
その後はsetAdapter()
メソッドを呼び出さないでください。
特定の範囲でアイテムに通知する必要があります
@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());
}
以下のような特定の範囲でアイテムに通知する必要があります。
@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());
}
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);
}
}
});
条件が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);
}
これで問題が解決するはずですが、loadMore
をaddOnScrollListener
に設定してRecyclerView
をActivityまたはPresenterクラス内で処理し、findFirstVisibleItemPosition
がLayoutManager
は0で、データをロードします。
私は1つ作成しました ページネーション用のライブラリ 、それを自由に使用またはカスタマイズしてください。
PS:他のユーザーが言及したように、notifyDataSetChanged
は使用しないでください。これにより、すべてのビューが更新され、表示したくないビューが更新されます。代わりにnotifyItemRangeInsert
を使用してください。 0からデータベースからロードされたデータのサイズまで。上からロードする場合、notifyDataSetChanged
はスクロール位置を新しくロードされたデータの最上部に変更するため、notifyItemRangeInsert
を使用してアプリを快適に使用する必要があります
私はこのようなことを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();
}
}
}
});
それが役に立てば幸い。
notifyItemRangeInserted
オペレーションには、RecyclerView.Adapter
のLoadMore
メソッドを使用することをお勧めします。リストに新しいアイテムのセットを追加すると、データセット全体に通知する必要がなくなります。
notifyItemRangeInserted(int positionStart, int itemCount)
登録されたオブザーバに、positionStartで始まる現在反映されているitemCountアイテムが新しく挿入されたことを通知します。
詳細: https://developer.Android.com/reference/Android/support/v7/widget/RecyclerView.Adapter.html