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

如何在Flutter中实现聊天应用的自定义聊天气泡组件?

聊天气泡参考效果图

Flutter 聊天气泡组件实现方案

核心实现思路

这种带侧边尖角的聊天气泡推荐用CustomPaint自定义绘制实现,灵活度最高,可适配各种尺寸、样式需求,不需要依赖第三方库。

具体实现步骤

1. 自定义气泡背景绘制类

通过CustomPainter绘制带尖角的气泡轮廓,支持左右两种方向适配发送/接收消息场景:

class ChatBubblePainter extends CustomPainter {
  final Color bgColor;
  final bool isSendByMe; // true为当前用户发送的气泡,显示在右侧
  final double radius;
  final double arrowSize;

  ChatBubblePainter({
    required this.bgColor,
    required this.isSendByMe,
    this.radius = 8,
    this.arrowSize = 10,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = bgColor;
    final path = Path();

    if (isSendByMe) {
      // 右侧气泡逻辑:尖角在右侧
      path.moveTo(radius, 0);
      path.lineTo(size.width - arrowSize - radius, 0);
      path.quadraticBezierTo(size.width - arrowSize, 0, size.width - arrowSize, radius);
      path.lineTo(size.width - arrowSize, size.height - radius - arrowSize/2);
      path.lineTo(size.width, size.height - arrowSize);
      path.lineTo(size.width - arrowSize, size.height - arrowSize/2);
      path.lineTo(size.width - arrowSize, size.height - radius);
      path.quadraticBezierTo(size.width - arrowSize, size.height, size.width - arrowSize - radius, size.height);
      path.lineTo(radius, size.height);
      path.quadraticBezierTo(0, size.height, 0, size.height - radius);
      path.lineTo(0, radius);
      path.quadraticBezierTo(0, 0, radius, 0);
    } else {
      // 左侧气泡逻辑:尖角在左侧
      path.moveTo(arrowSize + radius, 0);
      path.lineTo(size.width - radius, 0);
      path.quadraticBezierTo(size.width, 0, size.width, radius);
      path.lineTo(size.width, size.height - radius);
      path.quadraticBezierTo(size.width, size.height, size.width - radius, size.height);
      path.lineTo(arrowSize + radius, size.height);
      path.quadraticBezierTo(arrowSize, size.height, arrowSize, size.height - radius);
      path.lineTo(arrowSize, size.height - arrowSize/2);
      path.lineTo(0, size.height - arrowSize);
      path.lineTo(arrowSize, size.height - arrowSize/2);
      path.lineTo(arrowSize, radius);
      path.quadraticBezierTo(arrowSize, 0, arrowSize + radius, 0);
    }
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant ChatBubblePainter oldDelegate) {
    return bgColor != oldDelegate.bgColor || isSendByMe != oldDelegate.isSendByMe;
  }
}

2. 封装完整聊天气泡组件

把自定义背景和文本、头像等内容组合,封装成可直接调用的组件:

class ChatBubble extends StatelessWidget {
  final String content;
  final bool isSendByMe;
  final TextStyle? textStyle;

  const ChatBubble({
    super.key,
    required this.content,
    required this.isSendByMe,
    this.textStyle,
  });

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: isSendByMe ? MainAxisAlignment.end : MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.end,
      children: [
        // 对方消息左侧展示头像
        if (!isSendByMe) 
          const Padding(
            padding: EdgeInsets.only(right: 4),
            child: CircleAvatar(radius: 16, child: Icon(Icons.person, size: 16)),
          ),
        // 气泡主体
        CustomPaint(
          painter: ChatBubblePainter(
            bgColor: isSendByMe ? Colors.blueAccent : Colors.grey[200]!,
            isSendByMe: isSendByMe,
          ),
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
            // 限制气泡最大宽度为屏幕70%,避免过长内容撑满全屏
            constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.7),
            child: Text(
              content,
              style: textStyle ?? TextStyle(
                color: isSendByMe ? Colors.white : Colors.black87,
                fontSize: 14,
              ),
            ),
          ),
        ),
        // 自己发送的消息右侧展示头像
        if (isSendByMe)
          const Padding(
            padding: EdgeInsets.only(left: 4),
            child: CircleAvatar(radius: 16, child: Icon(Icons.person, size: 16)),
          ),
      ],
    );
  }
}

3. 调用示例

直接在聊天列表中循环调用即可:

ListView.builder(
  itemCount: 10,
  itemBuilder: (context, index) {
    final isSendByMe = index % 2 == 0;
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
      child: ChatBubble(
        content: isSendByMe ? "这是我发送的消息" : "这是我收到的消息,内容可以很长,会自动换行显示",
        isSendByMe: isSendByMe,
      ),
    );
  },
)

可扩展优化点

  • 支持富内容:将气泡内的Text替换为图片、视频、卡片等组件即可,背景绘制逻辑无需修改
  • 连续消息优化:同一用户连续发送的多条消息,可仅保留最后一条的尖角,更符合主流聊天APP交互习惯
  • 附加信息扩展:通过Stack组件可在气泡右下角添加发送时间、已读状态等附加信息

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 15:06:02