RecyclerView 滚动时弄乱视图 - 带图片

RecyclerView Messing Up Views When Scrolling - With Picture

我正在开发一个聊天应用程序。我有聊天 activity,两个用户可以在其中发送 WhatsApp 之类的消息,但我遇到了问题。

如图所示 (https://ibb.co/3cyYX01),滚动时视图乱七八糟,我想我知道为什么了。

查看这些帖子后: ,

我认为问题可能出在函数 onBindViewHolder 中的回收器视图适配器中,因为我在某些视图(VIEW.GONE 和 VIEW.VISIBLE)上使用了可见性选项,并且我认为这些视图正在以错误的可见性重绘。

此外,我在onBindViewHolder中使用了holder.setIsRecyclable(false),以检查是否是回收部分导致了问题,当我使用它时,它运行良好。

这是 RecyclerView 适配器:

    private const val SEND_LAYOUT = 0
private const val RECEIVED_LAYOUT = 1

class ChatRecyclerViewAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private lateinit var receiverUserPic: String
    private lateinit var messageList: List<Message>
    private lateinit var currentUserPic: String
    private lateinit var currentUserUID: String
    private lateinit var targetUID: String

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

        val viewHolder: RecyclerView.ViewHolder
        val view: View

        viewHolder = if (viewType == SEND_LAYOUT) {

            view = LayoutInflater.from(parent.context)
                .inflate(R.layout.sent_message_row, parent, false)
            SentViewHolder(view)

        } else {

            view = LayoutInflater.from(parent.context)
                .inflate(R.layout.recieved_message_row, parent, false)
            ReceivedViewHolder(view)

        }

        return viewHolder

    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

        //holder.setIsRecyclable(false)
        val currentMessage = messageList[position]

        if (holder.itemViewType == SEND_LAYOUT) {

            holder as SentViewHolder
            holder.bindSentRow(currentMessage)

        } else {

            holder as ReceivedViewHolder
            holder.bindReceivedRow(currentMessage)

        }

    }

    override fun getItemCount(): Int {
        return messageList.size
    }

    override fun getItemViewType(position: Int): Int {

        val currentMessage = messageList[position]

        return if (FirebaseAuth.getInstance().currentUser?.uid.equals(currentMessage.sender))
            SEND_LAYOUT
        else
            RECEIVED_LAYOUT

    }

    inner class SentViewHolder(private val itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindSentRow(message: Message) {

            val sentMessageTextView =
                itemView.findViewById<TextView>(R.id.sentMessage)
            val sentImage = itemView.findViewById<ImageView>(R.id.sentImage)
            val profileImage =
                itemView.findViewById<ImageView>(R.id.sentMessageProfilePicture)
            val sentIsSeenImageTextView =
                itemView.findViewById<TextView>(R.id.sentIsSeenImageTextView)
            val sentIsSeenTextView =
                itemView.findViewById<TextView>(R.id.sentIsSeenTextView)


            profileImage.setOnClickListener {

                val visitProfileIntent = Intent(it.context, VisitProfileActivity::class.java)
                visitProfileIntent.putExtra("targetUID", currentUserUID)
                it.context.startActivity(visitProfileIntent)

            }

            if (message.message.equals("Sent you an image") && !message.url.equals("")) {

                sentMessageTextView.visibility = View.GONE
                sentIsSeenImageTextView.visibility = View.VISIBLE
                sentIsSeenTextView.visibility = View.GONE
                sentImage.visibility = View.VISIBLE


                Glide.with(itemView.rootView).load(message.url)
                    .override(SIZE_ORIGINAL, SIZE_ORIGINAL)
                    .error(R.drawable.error_icon)
                    .placeholder(R.drawable.loading_icon)
                    .listener(object : RequestListener<Drawable?> {
                        override fun onLoadFailed(
                            @Nullable e: GlideException?,
                            model: Any,
                            target: Target<Drawable?>,
                            isFirstResource: Boolean
                        ): Boolean {
                            return false
                        }

                        override fun onResourceReady(
                            resource: Drawable?,
                            model: Any?,
                            target: Target<Drawable?>?,
                            dataSource: DataSource?,
                            isFirstResource: Boolean
                        ): Boolean {
                            return false
                        }
                    }).into(sentImage)



                if (adapterPosition == messageList.size - 1) {
                    sentIsSeenImageTextView.visibility = View.VISIBLE
                    sentIsSeenTextView.visibility = View.GONE

                    if (message.seen == true) {
                        sentIsSeenImageTextView.text = "Seen"
                    } else {
                        sentIsSeenImageTextView.text = "Sent"
                    }

                } else {
                    sentIsSeenImageTextView.visibility = View.GONE
                }

                

        } else {

            sentMessageTextView.visibility = View.VISIBLE
            sentMessageTextView.text = message.message
            sentIsSeenImageTextView.visibility = View.GONE

            if (adapterPosition == messageList.size - 1) {
                sentIsSeenTextView.visibility = View.VISIBLE
                sentIsSeenImageTextView.visibility = View.GONE

                if (message.seen == true) {
                    sentIsSeenTextView.text = "Seen"
                } else {
                    sentIsSeenTextView.text = "Sent"
                }

            }

        }


        Glide.with(itemView.rootView).load(currentUserPic).into(profileImage)


    }

}


inner class ReceivedViewHolder(private val itemView: View) : RecyclerView.ViewHolder(itemView) {

    fun bindReceivedRow(message: Message) {

        val receiveMessageTextView =
            itemView.findViewById<TextView>(R.id.receivedMessage)
        val receiveImage =
            itemView.findViewById<ImageView>(R.id.receivedImage)
        val receiveProfileImage =
            itemView.findViewById<ImageView>(R.id.receivedMessageProfileImage)

        receiveProfileImage.setOnClickListener {

            val visitProfileIntent = Intent(it.context, VisitProfileActivity::class.java)
            visitProfileIntent.putExtra("targetUID", targetUID)
            it.context.startActivity(visitProfileIntent)

        }

        if (message.message.equals("Sent you an image") && !message.url.equals("")) {

            receiveMessageTextView.visibility = View.GONE
            receiveImage.visibility = View.VISIBLE

            Glide.with(itemView.rootView).load(message.url).into(receiveImage)


        } else {

            receiveMessageTextView.visibility = View.VISIBLE
            receiveMessageTextView.text = message.message

        }

        Glide.with(itemView.rootView).load(receiverUserPic).into(receiveProfileImage)


    }

}

fun getMessageList(): List<Message> {
    return messageList
}

fun setMessagesList(
    newList: List<Message>,
    userProfilePic: String,
    userProfilePic1: String,
    currentUID: String,
    receiverUID: String
) {
    messageList = newList
    currentUserPic = userProfilePic
    receiverUserPic = userProfilePic1
    currentUserUID = currentUID
    targetUID = receiverUID
    notifyDataSetChanged()
}


}

Pastebin Link: https://pastebin.com/Ri5pUAdk

谢谢!

因为视图持有者正在被回收和重复使用,导致您的视图处于错误状态。

SendViewHolder class 中,您仅通过将可见性设置为可见来处理 if 块中 sentImage 的状态。因此,您还需要将其可见性设置为进入 else 块。

或者您可以先重置视图可见性,然后再显示它,如下所示。

fun bindSendRow(message: Message) {
    sentMessageTextView.visibility = View.GONE
    sentImage.visibilty = View.GONE
    if(shouldShowImage){
        sentImage.visibility = View.VISIBLE
    } else if(shouldShowText){
        sentMessageTextView.visibility = View.VISIBLE
    }
}

recyleView 的工作基于此,它回收 视图以显示列表。当您滚动时,超出屏幕的 views 不会被销毁,而是会再次被重新使用以显示新的列表项。因此,如果您更改视图的 visibility 或任何其他 属性 并且不在 onBindViewHolder 中再次重置它,那么它将显示在它被回收之前设置的所有属性.

fun bind(data: Data) {
    val textView = itemView.findViewById<TextView>(R.id.tvText)
    if(data.text.isEmpty()) {
        textView.visibility = View.GONE
    }
}

在上面的方法中,当文本为empty时,我们隐藏了textView,但我们没有在else条件中设置任何内容。因此,当我们将可见性设置为 goneviews 将被回收时,它们永远不会显示 textView,因为它正在重用 view。为了解决这个问题,我们必须分别为 truefalse 条件设置视图的属性。

fun bind(data: Data) {
    val textView = itemView.findViewById<TextView>(R.id.tvText)
    if(data.text.isEmpty()) {
        textView.visibility = View.GONE
    } else {
        textView.visibility = View.VISIBLE
    }
}

SentViewHolderReceivedViewHolder 中,您将 ImageView 的可见性设置为 visible

sentImage.visibility = View.VISIBLE

receiveImage.visibility = View.VISIBLE

但您永远不会将其设置为 gone

(message.message.equals("Sent you an image") && !message.url.equals(""))的else条件中,将ImageViewvisibility设为GONE。对所有其他视图也执行相同的操作,这样您就不会得到意外的 UI.