You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView的ChatAdapter触发IndexOutOfBoundsException问题求助

解决RecyclerView聊天适配器键盘输入崩溃问题

我开发的聊天界面RecyclerView适配器ChatAdapter包含发送消息、接收消息、输入指示器三种视图类型。应用正常运行无异常,但打开键盘输入内容时崩溃,抛出java.lang.IndexOutOfBoundsException: Index 8 out of bounds for length 8错误。尝试修改getItemViewType和getItemCount逻辑及相关方案后仍未解决,原适配器代码如下:

class ChatAdapter(private val currentUserId: Int) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val messages = mutableListOf<Message>()
    private var isTypingIndicatorVisible = false

    fun updateMessages(newMessages: List<Message>) {
        messages.addAll(newMessages)
        notifyDataSetChanged()
    }
    private val MESSAGE_SENT = 1
    private val MESSAGE_RECEIVED = 2
    private val TYPING_INDICATOR = 3

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            MESSAGE_SENT -> {
                val binding = ItemMessageSentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                SentMessageViewHolder(binding)
            }
            MESSAGE_RECEIVED -> {
                val binding = ItemMessageReceivedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                ReceivedMessageViewHolder(binding)
            }
            TYPING_INDICATOR -> {
                val binding = ItemTypingIndicatorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                TypingIndicatorViewHolder(binding)
            }
            else -> throw IllegalArgumentException("Invalid view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val message = messages[position]
        when (holder.itemViewType) {
            MESSAGE_SENT -> {
                (holder as SentMessageViewHolder).bind(message)
            }
            MESSAGE_RECEIVED -> {
                (holder as ReceivedMessageViewHolder).bind(message)
            }
            TYPING_INDICATOR -> {
                (holder as TypingIndicatorViewHolder).bind(message)
            }
        }
    }


    override fun getItemCount(): Int {
        return messages.size + if (isTypingIndicatorVisible) 1 else 0
    }

    override fun getItemViewType(position: Int): Int {
        val message = messages[position]
        return if (isTypingIndicatorVisible && position == messages.size) {
            TYPING_INDICATOR
        } else if (message.senderEmail == currentUserId.toString()) {
            MESSAGE_SENT
        } else {
            MESSAGE_RECEIVED
        }
    }

    fun showTypingIndicator(isVisible: Boolean) {
        isTypingIndicatorVisible = isVisible
        notifyDataSetChanged()
    }

    inner class TypingIndicatorViewHolder(private val binding: ItemTypingIndicatorBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(message: Message) {
            binding.tvTyping.visibility = View.VISIBLE
        }
    }
    inner class SentMessageViewHolder(private val binding: ItemMessageSentBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(message: Message) {
            binding.tvMsgSent.text = message.content
            val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
            val date = dateFormat.parse(message.date)
            binding.tvDateTime.text = SimpleDateFormat("hh:mm a", Locale.getDefault()).format(date)
        }
    }

    inner class ReceivedMessageViewHolder(private val binding: ItemMessageReceivedBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(message: Message) {
            binding.tvMsgReceived.text = message.content
            val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
            val date = dateFormat.parse(message.date)
            binding.tvDateTime.text = SimpleDateFormat("hh:mm a", Locale.getDefault()).format(date)
        }
    }
}

问题根源

崩溃的核心是输入指示器显示时,适配器的位置处理逻辑越界:

  1. getItemCount返回messages.size + 1,但getItemViewType会先通过messages[position]获取消息,当position等于messages.size(输入指示器的位置)时,这个索引超出了messages的最大范围(列表索引从0到messages.size-1)。
  2. onBindViewHolder同样在所有位置都尝试获取messages[position],输入指示器位置会直接触发数组越界异常。
  3. 输入指示器的bind方法不需要Message参数,传递无效对象属于冗余逻辑。

修复方案

对三个关键方法进行修正:

1. 修正getItemViewType逻辑

先判断当前位置是否是输入指示器,再处理消息类型:

override fun getItemViewType(position: Int): Int {
    return if (isTypingIndicatorVisible && position == messages.size) {
        TYPING_INDICATOR
    } else {
        val message = messages[position]
        if (message.senderEmail == currentUserId.toString()) {
            MESSAGE_SENT
        } else {
            MESSAGE_RECEIVED
        }
    }
}

2. 修正onBindViewHolder逻辑

仅在消息类型的位置访问messages,输入指示器位置跳过消息获取:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    when (holder.itemViewType) {
        MESSAGE_SENT -> {
            (holder as SentMessageViewHolder).bind(messages[position])
        }
        MESSAGE_RECEIVED -> {
            (holder as ReceivedMessageViewHolder).bind(messages[position])
        }
        TYPING_INDICATOR -> {
            (holder as TypingIndicatorViewHolder).bind()
        }
    }
}

3. 简化TypingIndicatorViewHolder的bind方法

移除无用的Message参数:

inner class TypingIndicatorViewHolder(private val binding: ItemTypingIndicatorBinding) : RecyclerView.ViewHolder(binding.root) {
    fun bind() {
        binding.tvTyping.visibility = View.VISIBLE
    }
}

修复后完整代码

class ChatAdapter(private val currentUserId: Int) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val messages = mutableListOf<Message>()
    private var isTypingIndicatorVisible = false

    fun updateMessages(newMessages: List<Message>) {
        messages.addAll(newMessages)
        notifyDataSetChanged()
    }
    private val MESSAGE_SENT = 1
    private val MESSAGE_RECEIVED = 2
    private val TYPING_INDICATOR = 3

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            MESSAGE_SENT -> {
                val binding = ItemMessageSentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                SentMessageViewHolder(binding)
            }
            MESSAGE_RECEIVED -> {
                val binding = ItemMessageReceivedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                ReceivedMessageViewHolder(binding)
            }
            TYPING_INDICATOR -> {
                val binding = ItemTypingIndicatorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                TypingIndicatorViewHolder(binding)
            }
            else -> throw IllegalArgumentException("Invalid view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder.itemViewType) {
            MESSAGE_SENT -> {
                (holder as SentMessageViewHolder).bind(messages[position])
            }
            MESSAGE_RECEIVED -> {
                (holder as ReceivedMessageViewHolder).bind(messages[position])
            }
            TYPING_INDICATOR -> {
                (holder as TypingIndicatorViewHolder).bind()
            }
        }
    }


    override fun getItemCount(): Int {
        return messages.size + if (isTypingIndicatorVisible) 1 else 0
    }

    override fun getItemViewType(position: Int): Int {
        return if (isTypingIndicatorVisible && position == messages.size) {
            TYPING_INDICATOR
        } else {
            val message = messages[position]
            if (message.senderEmail == currentUserId.toString()) {
                MESSAGE_SENT
            } else {
                MESSAGE_RECEIVED
            }
        }
    }

    fun showTypingIndicator(isVisible: Boolean) {
        isTypingIndicatorVisible = isVisible
        notifyDataSetChanged()
    }

    inner class TypingIndicatorViewHolder(private val binding: ItemTypingIndicatorBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind() {
            binding.tvTyping.visibility = View.VISIBLE
        }
    }
    inner class SentMessageViewHolder(private val binding: ItemMessageSentBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(message: Message) {
            binding.tvMsgSent.text = message.content
            val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
            val date = dateFormat.parse(message.date)
            binding.tvDateTime.text = SimpleDateFormat("hh:mm a", Locale.getDefault()).format(date)
        }
    }

    inner class ReceivedMessageViewHolder(private val binding: ItemMessageReceivedBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(message: Message) {
            binding.tvMsgReceived.text = message.content
            val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
            val date = dateFormat.parse(message.date)
            binding.tvDateTime.text = SimpleDateFormat("hh:mm a", Locale.getDefault()).format(date)
        }
    }
}

内容的提问来源于stack exchange,提问作者Sahar Batool

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 15:04:50