[英]unwanted scroll in reverse layout RecyclerView on notifyDataSetChanged
[英]Recyclerview reverse layout starting scroll position issue
我正在創建一個聊天應用程序並顯示我正在使用回收站視圖的消息。 最新消息顯示在底部。 用戶向上滾動以查看更多消息。
加載聊天屏幕時,視圖不會從最底部開始,並且最新消息不可見。 用戶必須向下滾動幾行才能看到最新消息。 這是一個糟糕的 UI,最新的消息應該在屏幕的末尾/底部可見。
我正在使用setReverseLayout(true)
和setStackFromEnd(false)
,並且我在網上搜索了類似的問題,但沒有運氣。 現在,我在延遲設置回收站視圖后立即將滾動 position 設置為 0,但這並不總是有效,而且很跳躍。
如果我正常設置 recyclerview (不使用setReverseLayout
和setStackFromEnd
),最新的消息每次都完美地加載在頂部。
這是啟動回收器視圖的代碼:
RecyclerView recyclerView = findViewById(R.id.recyclerViewMessageRoom);
adapter = new RA_MessageRoom(this, userFirebaseUid, messagesGroupedByDate, messageUsers);
recyclerView.setAdapter(adapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setReverseLayout(true);
layoutManager.setStackFromEnd(false);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
// -- Workaround with delay - still doesn't completely work
new Handler().postDelayed(() -> {
recyclerView.scrollToPosition(0);
}, 200);
遇到過這個問題並知道如何解決的請分享。 謝謝。
編輯(2019 年 7 月 6 日):
這是回收站適配器的代碼。 提醒一下,如果我刪除反向布局設置,它工作得很好。
RA_MessageRoom:
public class RA_MessageRoom extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "RA_MessageRoom";
private static final int TYPE_USER = 1;
private static final int TYPE_PARTICIPANT = 2;
private Context context;
private String userFirebaseUid;
private List<MessagesDateGrouper> messagesGroupedByDate;
private HashSet<MessagesUserModel> messageUsers;
public RA_MessageRoom(Context context, String userFirebaseUid, List<MessagesDateGrouper> messagesGroupedByDate, HashSet<MessagesUserModel> messageUsers) {
this.context = context;
this.userFirebaseUid = userFirebaseUid;
this.messagesGroupedByDate = messagesGroupedByDate;
this.messageUsers = messageUsers;
}
@Override
public int getItemViewType(int position) {
if (messagesGroupedByDate.get(position).getViewType() == MessagesDateGrouper.TYPE_CHAT) {
MessageChatItem message = (MessageChatItem) messagesGroupedByDate.get(position);
if (message.getMessages().getSenderFirebaseUid().equals(userFirebaseUid)) {
return TYPE_USER;
} else {
return TYPE_PARTICIPANT;
}
} else {
return messagesGroupedByDate.get(position).getViewType();
}
}
@Override
public int getItemCount() {
return messagesGroupedByDate != null ? messagesGroupedByDate.size() : 0;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final RecyclerView.ViewHolder holder;
View view;
switch (viewType) {
case MessagesDateGrouper.TYPE_DATE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_message_room_separator, parent, false);
holder = new MessageRoomDateVH(view);
break;
case TYPE_USER:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_message_room_user, parent, false);
holder = new MessageRoomUserVH(view);
break;
case TYPE_PARTICIPANT:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_message_room_participant, parent, false);;
holder = new MessageRoomParticipantVH(view);
break;
default:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_message_room_user, parent, false);;
holder = new MessageRoomUserVH(view);
break;
}
return holder;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof MessageRoomDateVH) {
MessageDateItem date = (MessageDateItem) messagesGroupedByDate.get(position);
((MessageRoomDateVH)holder).date.setText(date.getDate());
} else if (holder instanceof MessageRoomParticipantVH) {
MessageRoomParticipantVH view = (MessageRoomParticipantVH) holder;
MessageChatItem messageItem = (MessageChatItem) messagesGroupedByDate.get(position);
MessagesModel message = messageItem.getMessages();
view.name.setText(context.getString(R.string.unknown));
view.profileImage.setImageResource(R.drawable.default_profile_image_grey);
for (MessagesUserModel user: messageUsers) {
if (user.getFirebaseId().equals(message.getSenderFirebaseUid())) {
int fallbackImage;
if (user.getMerchant() == null || !user.getMerchant()) {
fallbackImage = R.drawable.default_profile_image_grey;
} else {
fallbackImage = R.drawable.store_profile;
}
GlideApp.with(context)
.load(user.getPhotoThumbUrl())
.placeholder(R.drawable.placeholder)
.fallback(fallbackImage)
.into(view.profileImage);
view.name.setText(user.getName());
break;
}
}
if (message.getImageUrl() != null && !message.getImageUrl().equals("") ) {
view.image.setVisibility(View.VISIBLE);
view.imageSpinner.setVisibility(View.VISIBLE);
view.chat.setVisibility(View.GONE);
view.image.setClipToOutline(true);
GlideApp.with(context)
.load(message.getImageUrl())
.placeholder(R.drawable.placeholder_message)
.fallback(R.drawable.placeholder_message)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
view.imageSpinner.setVisibility(View.GONE);
return false;
}
})
.into(view.image);
} else {
view.chat.setVisibility(View.VISIBLE);
view.image.setVisibility(View.GONE);
view.imageSpinner.setVisibility(View.GONE);
view.chat.setText(message.getMessageText());
}
String time = DateFormatService.messageRoomParseDateToTimeString(message.getDate());
view.date.setText(time);
} else if (holder instanceof MessageRoomUserVH){
MessageRoomUserVH view = (MessageRoomUserVH) holder;
MessageChatItem messageItem = (MessageChatItem) messagesGroupedByDate.get(position);
MessagesModel message = messageItem.getMessages();
if (message.getImageUrl() != null && !message.getImageUrl().equals("") ) {
view.image.setVisibility(View.VISIBLE);
view.imageSpinner.setVisibility(View.VISIBLE);
view.chat.setVisibility(View.GONE);
view.image.setClipToOutline(true);
GlideApp.with(context)
.load(message.getImageUrl())
.placeholder(R.drawable.placeholder_message)
.fallback(R.drawable.placeholder_message)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
view.imageSpinner.setVisibility(View.GONE);
return false;
}
})
.into(view.image);
} else {
view.chat.setVisibility(View.VISIBLE);
view.image.setVisibility(View.GONE);
view.imageSpinner.setVisibility(View.GONE);
view.chat.setText(message.getMessageText());
}
String time = DateFormatService.messageRoomParseDateToTimeString(message.getDate());
view.date.setText(time);
}
}
public class MessageRoomDateVH extends RecyclerView.ViewHolder {
TextView date;
public MessageRoomDateVH(@NonNull View itemView) {
super(itemView);
date = itemView.findViewById(R.id.textMessageRoomDateSection);
}
}
public class MessageRoomParticipantVH extends RecyclerView.ViewHolder {
ImageView profileImage;
TextView name;
TextView chat;
ImageView image;
TextView date;
ProgressBar imageSpinner;
public MessageRoomParticipantVH(@NonNull View itemView) {
super(itemView);
profileImage = itemView.findViewById(R.id.imageMessageRoomParticipantProfile);
name = itemView.findViewById(R.id.textMessageRoomParticipantName);
chat = itemView.findViewById(R.id.textMessageRoomParticipantChat);
image = itemView.findViewById(R.id.imageMessageRoomParticipantImage);
date = itemView.findViewById(R.id.textMessageRoomParticipantDate);
imageSpinner = itemView.findViewById(R.id.progressBarMessageRoomParticipantImage);
}
}
public class MessageRoomUserVH extends RecyclerView.ViewHolder {
TextView chat;
ImageView image;
TextView date;
ProgressBar imageSpinner;
public MessageRoomUserVH(@NonNull View itemView) {
super(itemView);
chat = itemView.findViewById(R.id.textMessageRoomUserChat);
image = itemView.findViewById(R.id.imageMessageRoomUserImage);
date = itemView.findViewById(R.id.textMessageRoomUserDate);
imageSpinner = itemView.findViewById(R.id.progressBarMessageRoomUserImage);
}
}
}
編輯:
盡管首選是將 stackFromEnd 設置為 false,但我最終將 stackFromEnd 從 false 更改為 true,消除了延遲,並按照接受的答案的建議繼續設置滾動 position 以解決此問題。
更新的工作代碼:
RecyclerView recyclerView = findViewById(R.id.recyclerViewMessageRoom);
adapter = new RA_MessageRoom(this, userFirebaseUid, messagesGroupedByDate, messageUsers);
recyclerView.setAdapter(adapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setReverseLayout(true);
layoutManager.setStackFromEnd(true);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
recyclerView.scrollToPosition(0);
謝謝您的幫助!
您還需要在第一頁加載時處理消息加載,並且接收到的新消息位於 recyclerview 的底部:
if (pageLoad) {
list.add(Model)
} else {
// Add to the top of the list (since list is reverse message will come at the bottom)
list.add(0, Model)
}
這很容易,您只需添加一行:-
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
看到這個答案:
並為您的RA_MessageRoom
創建一個 setter 以更新您的messagesGroupedByDate
。 類似的東西:
Collections.reverse(messagesGroupedByDate); // Reverse your dataset like in answer above
adapter.setMessagesGroupedByDate(messagesGroupedByDate); // Update your dataset in adapter
adapter.notifyDataSetChanged(); // Notify your adapter
每次收到新消息時,您的列表都會更新。 您需要將此代碼段放入您的數據提取中,然后您可以刪除您的 postDelayed 處理程序。
您可以在 xml 和反向布局中設置布局管理器。 當我們設置反向布局時,我們發現了這個問題。 我們可以通過添加 setStackFromEnd =true 來解決它
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="vertical"
app:reverseLayout="true"
app:stackFromEnd="true"
注意:接受的答案會誤導您。
關於LinearLayoutManager
的上市熱情有 4 種可能性。
1.
startStackFromEnd=true
reverseLayout=true
2.
startStackFromEnd=false
reverseLayout=false
3.
startStackFromEnd=true
reverseLayout=false
4.
startStackFromEnd=false //best for chatting
reverseLayout=true //applications
每種組合的作用不同我不知道你的要求到底是什么,所以圍繞這些值進行調整,我相信你會得到你想要的。
看起來像是回收器視圖實現中的錯誤,或者更確切地說是該線性布局管理器中的錯誤。
要解決此問題,您需要確保當您的回收站可以滾動時stackFromEnd
為真。 如果您的回收站沒有足夠的項目可以滾動,而您仍然希望它們位於屏幕底部,那么如果回收站無法滾動, stackFromEnd
設置為 false。
要判斷您的視圖是否可以滾動,您可以使用 Kotlin 擴展:
/**
* Tells if this view can scroll vertically.
* This view may still contain children who can scroll.
*/
fun View.canScrollVertically() = this.let {
it.canScrollVertically(-1) || it.canScrollVertically(1)
}
在Fulguris 中,我使用以下函數來修復該錯誤,訣竅是找到在代碼中調用它的正確位置:
/**
* Workaround reversed layout bug: https://github.com/Slion/Fulguris/issues/212
*/
fun fixScrollBug(aList : RecyclerView): Boolean {
val lm = (aList.layoutManager as LinearLayoutManager)
// Can't change stackFromEnd when computing layout or scrolling otherwise it throws an exception
if (!aList.isComputingLayout) {
if (aList.context.configPrefs.toolbarsBottom) {
// Workaround reversed layout bug: https://github.com/Slion/Fulguris/issues/212
if (lm.stackFromEnd != aList.canScrollVertically()) {
lm.stackFromEnd = !lm.stackFromEnd
return true
}
} else {
// Make sure this is set properly when not using bottom toolbars
// No need to check if the value is already set properly as this is already done internally
lm.stackFromEnd = false
}
}
return false
}
我猜你可以用一個針對lm.reverseLayout
檢查替換toolbarsBottom
上的特定於應用程序的檢查。
有同樣的問題 - 提交列表總是會導致顯示的項目位於列表中間的某個位置,而不是第一個項目顯示在底部。 這發生在 app:reverseLayout="true" 但經常用於 non-reversed 時。 意識到我的 recyclerview 在約束布局內的高度為 0dp,約束從頂部到父級,從底部到父級。 將高度更改為 match_parent 並且它可以正常工作 - 現在底部的第一項是提交列表時的起點。
如果您想要與默認情況下使用 Recycler 視圖時相同的可靠性(行為),即startStackFromEnd=false
和reverseLayout=false
,我發現的最佳解決方案是將180 rotation
應用於回收器視圖,並將其應用於適配器項。
所以在你的片段 class 你會有類似的東西
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
//code...
yourRecyclerView.rotation = 180F
//code...
}
並在您的適配器(帶有視圖或數據綁定)中類似於
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
//code...
val binding = //inflation...
binding.root.rotation = 180F
//code
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.