Android-RecyclerView在添加新项目后显示错误的项目



我正在开发一个基于WebSocket的聊天应用程序,在向RV(RecyclerView)添加新的传入消息时遇到了问题。

我有以下适配器的覆盖功能

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): UserChatMessagesAdapter.Holder {
val binding = AdapterUserChatMessagesRvBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return Holder(binding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bindData(messages[position])
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
override fun getItemCount() = messages.size

数据来自API调用,由ViewModel通过LiveData发布。

出于测试目的,在第一次连接时,我只返回10条要显示的消息。然后,当滚动时,我返回下面的10,依此类推

我在从滚动时超过。

添加新消息的问题

问题是,当用户A向用户B(反之亦然)发送消息时,消息显示不正确。

例如,假设我已经显示了消息:M1,M2。。。,M10.

然后用户A发送M11。但它没有显示新的M11,而是显示另一个,比如M3(或者任何其他,我还没有检测到图案)。

适配器显示新消息的方法:

fun newMessage(newMessage: ChatMessageView) {
val index = LAST_MESSAGE_INDEX
messages.add(index, newMessage)
notifyItemInserted(index)
}

调试时,我没有发现任何错误。newMessage值为期望的一个和CCD_ 3列表被正确地更新。我这似乎不是数据问题。

消息缓存在内存中,因此当重新输入聊天,则不会再次向API请求它们。话虽如此,当我重新进入聊天室,消息显示正确。

我也尝试过:

  • 使用notifyDataSetChanged
  • 替换整个messages列表
  • 平滑滚动条(我在某个地方读到过,这可能是一个与快速用户滚动有关的问题)
  • 重写方法getItemViewTypegetItemId
  • 设置adapter.setHasStableIds(true)

如果有帮助,这是Holder的定义:

inner class Holder(private val binding: AdapterUserChatMessagesRvBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: ChatMessageView) {
if (message.isSentByMe) {
binding.userChatMessagesAdapterSentMessage.configure(
SentMessageComponent.Configuration(
message.content,
message.sentMoment.time
)
)
binding.userChatMessagesAdapterSentMessage.visible()
} else {
binding.userChatMessagesAdapterReceivedMessage.configure(
ReceivedMessageComponent.Configuration(
message.content,
message.sentMoment.time
)
)
binding.userChatMessagesAdapterReceivedMessage.visible()
}
configureDateSeparator(message.sentMoment.date)
}

这是我自定义的RecyclerView.OnScrollListener定义:

internal abstract class PaginationScrollListener(
private val layoutManager: LinearLayoutManager
) : RecyclerView.OnScrollListener() {
abstract fun isLastPage(): Boolean
abstract fun isLoading(): Boolean
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!isLoading() && !isLastPage()) {
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreItems()
}
}
}
abstract fun loadMoreItems()
}

--------编辑-1--------

我添加了相应的XML:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/adapter_chat_room_main_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="10dp">
<...components.ChatDateSeparatorComponent
android:id="@+id/user_chat_messages_adapter_date_separator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<...components.ReceivedMessageComponent
android:id="@+id/user_chat_messages_adapter_received_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_chat_messages_adapter_date_separator" />
<...components.SentMessageComponent
android:id="@+id/user_chat_messages_adapter_sent_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_chat_messages_adapter_date_separator" />
</androidx.constraintlayout.widget.ConstraintLayout>

您的bindData函数根据消息类型使一个组件可见,但不会使另一个组件不可见-因此,如果ViewHolder用于显示isSentByMetrue的项目,然后向下滚动并将其重新用于isSentByMefalse的项目,则userChatMessagesAdapterSentMessageuserChatMessagesAdapterReceivedMessage都将可见。

我猜在您的布局中有两个不同的View——您还没有显示XML,但ViewHolder的高度可能是固定的(而不是wrap_content),所以这两个视图中较低的一个在可见区域之外吗?或者一个覆盖另一个,所以你只能看到顶部的那个?


编辑:是的,看,你把它们叠加在一起:

<...components.ChatDateSeparatorComponent
android:id="@+id/user_chat_messages_adapter_date_separator"
app:layout_constraintTop_toTopOf="parent"/>
<...components.ReceivedMessageComponent
android:id="@+id/user_chat_messages_adapter_received_message"
android:layout_width="match_parent"
...
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_chat_messages_adapter_date_separator" />
<...components.SentMessageComponent
android:id="@+id/user_chat_messages_adapter_sent_message"
android:layout_width="match_parent"
...
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_chat_messages_adapter_date_separator" />

两个的顶部都被约束到固定在顶部的separator视图的底部,因此在垂直方向上它们处于完全相同的位置。一个被限制在左边,另一个被约束在右边,但它们的宽度都是match_parent,所以它们填充了整个宽度。

如果这两个MessageComponent视图都是visible,则只会看到SentMessage视图,因为它是最后指定的(绘制在另一个视图的顶部,因为它具有更高的Z阶)。如果ReceivedMessage的内容明显更多,使其更高(因为它们的高度都是wrap_content),那么它会从另一个下面向外窥视。

正如我所说,您只是在发布的代码中将它们设置为可见。您从未将另一个设置为不可见/已消失以确保其隐藏。你不能在显示一个或另一个之间切换,你显示一个,然后它保持可见,即使ViewHolder需要显示另一种类型的消息。因此,根据您的XML,如果发送的消息显示在ViewHolder中,您将无法看到任何接收的发送内容。

这就是为什么您可能会看到M3而不是M11——因为之前显示M3的VH正在被重用,所以会显示M11,但您无法在旧的M311,它还没有被用来显示发送的消息,所以它看起来不错(但如果你再次上下滚动,东西可能会坏掉)。


如果您不知道,RecyclerView的目的是重用(回收)少数ViewHolder,使用它们来显示特定项目的数据。在messages0中,您将获得其中一个ViewHolder,可能已用于显示其他内容,您将其设置为显示当前项目。

粗体的这一点很重要,因为这意味着VH将具有旧状态,在您的情况下,包括设置为可见的View。因此,您必须显式设置所有的状态,这样就不会留下旧状态了——假设它是脏的,需要完全设置。也就是说,当您将其中一个View设置为VISIBLE时,必须将另一个设置为INVISIBLEGONE。否则,如果它以前是可见的,现在你有两个可见的东西,这是一个问题!

所以你必须这样做:

if (message.isSentByMe) {
binding.userChatMessagesAdapterSentMessage.configure(
SentMessageComponent.Configuration(
message.content,
message.sentMoment.time
)
)
binding.userChatMessagesAdapterSentMessage.visible()
// explicitly hide the other one - I'm assuming you'll write another function
binding.userChatMessagesAdapterReceivedMessage.invisible()
} else {
binding.userChatMessagesAdapterReceivedMessage.configure(
ReceivedMessageComponent.Configuration(
message.content,
message.sentMoment.time
)
)
binding.userChatMessagesAdapterReceivedMessage.visible()
// same as the other branch - you're showing this, make sure the other is hidden
binding.userChatMessagesAdapterSentMessage.invisible()
}

相关内容

  • 没有找到相关文章

最新更新