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

Android Kotlin聊天应用RecyclerView项背景复用圆角异常问题

问题:复用Drawable导致RecyclerView消息条目背景绘制异常(圆角消失、文字溢出)

我用Kotlin开发Android聊天应用,用RecyclerView展示消息条目。消息条目有8种不同背景,取决于两个条件:

  • 是否为本人发送
  • 在时间分组中的位置(首项/中间项/末项/单独项)

最初在onBindViewHolder()里每次用ContextCompat.getDrawable()获取Drawable设置背景,显示完全正常;但为了优化性能,我把所有Drawable提前初始化作为Adapter的成员变量复用后,就出现了部分圆角消失、文字溢出背景的绘制异常。

初始正常代码:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val message = ... 
    textTV.background = ContextCompat.getDrawable(
        context,
        when {
            msg.isLastInTimeGroup() && !msg.sentByMe -> R.drawable.chat_message_in_last_bg
            msg.isFirstInTimeGroup() && !msg.sentByMe -> R.drawable.chat_message_in_first_bg
            msg.isMiddleInTimeGroup() && !msg.sentByMe -> R.drawable.chat_message_in_middle_bg
            msg.isLastInTimeGroup() && msg.sentByMe -> R.drawable.chat_message_out_last_bg
            msg.isFirstInTimeGroup() && msg.sentByMe -> R.drawable.chat_message_out_first_bg
            msg.isMiddleInTimeGroup() && msg.sentByMe -> R.drawable.chat_message_out_middle_bg
            else -> if (msg.sentByMe) R.drawable.chat_message_out_bg else R.drawable.chat_message_in_bg
        }
    )
}

复用Drawable后的异常代码:

private val inFirstBg: Drawable? = ContextCompat.getDrawable(ctx, R.drawable.chatMessageInFirstBg)
private val inMiddleBg: Drawable? = ContextCompat.getDrawable(ctx, R.drawable.chatMessageInMiddleBg)
private val inLastBg: Drawable? = ContextCompat.getDrawable(ctx, R.drawable.chatMessageInLastBg)
private val inAloneBg: Drawable? = ContextCompat.getDrawable(ctx, R.drawable.chatMessageInBg)
private val outFirstBg: Drawable? = ContextCompat.getDrawable(ctx, R.drawable.chatMessageOutFirstBg)
private val outMiddleBg: Drawable? = ContextCompat.getDrawable(ctx, R.drawable.chatMessageOutMiddleBg)
private val outLastBg: Drawable? = ContextCompat.getDrawable(ctx, R.drawable.chatMessageOutLastBg)
private val outAloneBg: Drawable? = ContextCompat.getDrawable(ctx, R.drawable.chatMessageOutBg)

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val message = ... 
    textTV.background = when {
        msg.isLastInTimeGroup() && !msg.sentByMe -> inLastBg
        msg.isFirstInTimeGroup() && !msg.sentByMe -> inFirstBg
        msg.isMiddleInTimeGroup() && !msg.sentByMe -> inMiddleBg
        msg.isLastInTimeGroup() && msg.sentByMe -> outLastBg
        msg.isFirstInTimeGroup() && msg.sentByMe -> outFirstBg
        msg.isMiddleInTimeGroup() && msg.sentByMe -> outMiddleBg
        else -> if (msg.sentByMe) outAloneBg else inAloneBg
    }
}

背景资源示例(out_middle):

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="@color/blue" />
    <corners android:radius="24dp" android:topRightRadius="4dp" android:bottomRightRadius="4dp"/>
</shape>

想请教下为什么复用Drawable会导致这些绘制异常?问题出在哪里?


回答

嘿,这个坑我之前踩过!核心原因是同一个Drawable实例不能同时被多个View复用——Drawable是带状态的对象,当你把它设置给一个View后,View会修改它的内部状态(比如尺寸边界、圆角渲染参数),当另一个View再使用这个实例时,就会继承之前被修改过的状态,直接导致各种绘制异常。

具体拆解下问题:

  1. Drawable状态共享冲突:你初始化的那些全局Drawable实例是单例,RecyclerView的ViewHolder本身就是复用机制,第一个ViewHolder设置这个Drawable后,它的bounds(边界)会被调整为当前View的大小;当这个Drawable被传给下一个ViewHolder的View时,它会带着之前的边界状态,新View的背景尺寸就会错乱,看起来就是文字溢出或者背景变形。
  2. 圆角参数被篡改:像你用的shape类型Drawable,它的圆角参数会随着View的尺寸变化、绘制流程被动态调整,复用同一个实例时,后面的View就会用上被改坏的圆角设置,自然出现圆角消失或异常的情况。

给你两个靠谱的解决思路:

方案1:每次使用时创建Drawable副本

不要直接复用原始Drawable,而是每次在onBindViewHolder里获取它的独立副本,确保每个View拿到的都是干净的实例:

// 只存资源ID,不存Drawable实例
private fun getBgResId(msg: Message): Int {
    return when {
        msg.isLastInTimeGroup() && !msg.sentByMe -> R.drawable.chat_message_in_last_bg
        msg.isFirstInTimeGroup() && !msg.sentByMe -> R.drawable.chat_message_in_first_bg
        msg.isMiddleInTimeGroup() && !msg.sentByMe -> R.drawable.chat_message_in_middle_bg
        msg.isLastInTimeGroup() && msg.sentByMe -> R.drawable.chat_message_out_last_bg
        msg.isFirstInTimeGroup() && msg.sentByMe -> R.drawable.chat_message_out_first_bg
        msg.isMiddleInTimeGroup() && msg.sentByMe -> R.drawable.chat_message_out_middle_bg
        else -> if (msg.sentByMe) R.drawable.chat_message_out_bg else R.drawable.chat_message_in_bg
    }
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val message = ... 
    // 用mutate()创建可变副本,避免和系统缓存的Drawable共享状态
    val bgDrawable = ContextCompat.getDrawable(context, getBgResId(msg))?.mutate()
    textTV.background = bgDrawable
}

这里的mutate()是关键,它会生成一个独立的Drawable实例,彻底避免状态共享问题。

方案2:用DataBinding绑定资源ID(更优雅)

如果项目用了DataBinding,可以直接在布局里绑定背景资源ID,让系统自动处理Drawable的复用和状态管理:

<!-- 消息条目布局 -->
<TextView
    android:id="@+id/textTV"
    android:background="@{msg.getBgResId()}"
    ... />

然后在Message类或自定义BindingAdapter里实现getBgResId()方法,系统会自动为每个View创建合适的Drawable实例,既保证性能又不会有状态冲突。

补充一句:你最初的代码虽然每次获取Drawable,但系统内部有Drawable缓存,性能并不会差太多;反而直接复用实例的优化方向错了,才引入了bug,优化性能也要找对场景哦~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:57:03