搜索插件、代码仓与 APP 分类

chat_bubbles 封面
插件详情v1.10.1

chat_bubbles

Flutter 聊天气泡小部件,类似于 WhatsApp 及更多形状。易于使用和实现聊天气泡。

AndroidiOSWebmacOSWindowsLinux
近 30 天下载
2.3万
喜欢数
577
Pub 分数
160

安装插件

flutter pub add chat_bubbles

插件介绍

基于插件公开信息、社区数据与项目使用情况整理的选型参考。

Chat Bubbles 是一个 Flutter 包,提供可自定义的聊天气泡小部件,灵感来自 WhatsApp 等流行的即时通讯应用。它支持多种形状和样式,便于在多个平台上实现逼真的聊天界面。该包轻量且易于集成,为开发者提供快速访问视觉上吸引人的消息气泡的途径,设置简单。非常适合需要动态、交互式聊天 UI 的应用。

适合场景

  • 即时通讯应用
  • 客户支持聊天界面
  • 群组聊天功能
  • 实时聊天原型
  • 教育类应用程序讨论区

主要特点

  • 多个气泡形状
  • 可自定义的颜色和边框
  • 支持不同的消息对齐方式
  • 与现有用户界面的轻松集成
  • 跨平台兼容性

适合对象

  • 开发聊天应用的开发者
  • 寻求现成聊天组件的UI设计师
  • 需要响应式消息气泡的项目
  • 初学者学习Flutter UI设计
  • 针对移动和网络平台的应用程序

使用建议

  • 仅限基本聊天UI元素
  • 无内置消息发送或存储
  • 需要手动处理消息状态
  • 未针对大型消息列表进行优化
  • 依赖外部逻辑实现实时功能

原始 README

以下为关联源码仓库的 README 原文快照,最新内容请访问源码仓库。

chat_bubbles plugin

Pub Version CI GitHub GitHub forks GitHub Repo stars GitHub last commit likes downloads pub points

Flutter chat bubble widgets, similar to the Whatsapp and more shapes. Audio and Image chat bubble widgets are also included. Easy to use and implement chat bubbles.

Getting Started

Add this to your package's pubspec.yaml file:

dependencies:
  chat_bubbles: ^1.10.1

Usage

Then you just have to import the package with

import 'package:chat_bubbles/chat_bubbles.dart'

Now you can use this plugin to implement various types of Chat Bubbles, Audio Chat Bubbles and Date chips.

Examples

iMessage's bubble example
BubbleSpecialThree(
  text: 'Added iMessage shape bubbles',
  color: Color(0xFF1B97F3),
  tail: false,
  textStyle: TextStyle(
      color: Colors.white,
      fontSize: 16
  ),
),
BubbleSpecialThree(
  text: 'Please try and give some feedback on it!',
  color: Color(0xFF1B97F3),
  tail: true,
  textStyle: TextStyle(
    color: Colors.white,
    fontSize: 16
  ),
),
BubbleSpecialThree(
  text: 'Sure',
  color: Color(0xFFE8E8EE),
  tail: false,
  isSender: false,
),
BubbleSpecialThree(
  text: "I tried. It's awesome!!!",
  color: Color(0xFFE8E8EE),
  tail: false,
  isSender: false,
),
BubbleSpecialThree(
  text: "Thanks",
  color: Color(0xFFE8E8EE),
  tail: true,
  isSender: false,
)
Single bubble example
  BubbleSpecialOne(
    text: 'Hi, How are you? ',
    isSender: false,
    color: Colors.purple.shade100,
    textStyle: TextStyle(
      fontSize: 20,
      color: Colors.purple,
      fontStyle: FontStyle.italic,
      fontWeight: FontWeight.bold,
    ),
  ),
Audio chat bubble example
  Duration duration = new Duration();
  Duration position = new Duration();
  bool isPlaying = false;
  bool isLoading = false;
  bool isPause = false;

  BubbleNormalAudio(
    color: Color(0xFFE8E8EE),
    duration: duration.inSeconds.toDouble(),
    position: position.inSeconds.toDouble(),
    isPlaying: isPlaying,
    isLoading: isLoading,
    isPause: isPause,
    onSeekChanged: _changeSeek,
    onPlayPauseButtonClick: _playAudio,
    sent: true,
  ),
Image chat bubble example
  BubbleNormalImage(
    id: 'id001',
    image: _image(),
    color: Colors.purpleAccent,
    tail: true,
    delivered: true,
  ),
Date Chip example
  DateChip(
    date: new DateTime(2021, 5, 7),
    color: Color(0x558AD3D5),
  ),
Message bar example
MessageBar(
    onSend: (_) => print(_),
    actions: [
      InkWell(
        child: Icon(
          Icons.add,
          color: Colors.black,
          size: 24,
        ),
        onTap: () {},
      ),
      Padding(
        padding: EdgeInsets.only(left: 8, right: 8),
        child: InkWell(
          child: Icon(
            Icons.camera_alt,
            color: Colors.green,
            size: 24,
          ),
          onTap: () {},
        ),
      ),
    ],
  ),

New in v1.8.0

Reply/Quote Bubble example
BubbleReply(
  repliedMessage: 'This is the original message',
  repliedMessageSender: 'John Doe',
  text: 'This is my reply!',
  isSender: false,
  color: Color(0xFF1B97F3),
  replyBorderColor: Colors.white,
  textStyle: TextStyle(color: Colors.white, fontSize: 16),
  sent: true,
),
Typing Indicator example
TypingIndicator(
  showIndicator: true,
  bubbleColor: Color(0xFFE8E8EE),
  dotColor: Colors.black54,
),

// Alternative wave animation style
TypingIndicatorWave(
  showIndicator: true,
  bubbleColor: Color(0xFFE8E8EE),
  dotColor: Colors.black54,
),
Link Preview Bubble example
BubbleLinkPreview(
  url: 'https://flutter.dev',
  title: 'Flutter - Build apps for any screen',
  description: 'Flutter transforms the app development process.',
  imageUrl: 'https://storage.googleapis.com/cms-storage-bucket/70760bf1e88b184bb1bc.png',
  text: 'Check out this awesome framework!',
  isSender: false,
  color: Color(0xFF1B97F3),
  textStyle: TextStyle(color: Colors.white, fontSize: 16),
),
Bubble Reactions example
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    BubbleNormal(
      text: 'Great work! 🎉',
      isSender: false,
      color: Color(0xFF1B97F3),
    ),
    BubbleReaction(
      reactions: [
        Reaction(emoji: '👍', count: 3, isUserReacted: true),
        Reaction(emoji: '❤️', count: 2),
        Reaction(emoji: '🎉', count: 1),
      ],
      onReactionTap: (reaction) {
        print('Tapped on ${reaction.emoji}');
      },
      onAddReactionTap: () {
        print('Add reaction tapped');
      },
    ),
  ],
),
Main example (Chat View)

Checkout the plugin example to figure out more.

Duration duration = new Duration();
Duration position = new Duration();
bool isPlaying = false;
bool isLoading = false;
bool isPause = false;

@override
Widget build(BuildContext context) {
  final now = new DateTime.now();
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: Stack(
      children: [
        SingleChildScrollView(
          child: Column(
            children: <Widget>[
              BubbleNormalImage(
                  id: 'id001',
                  image: _image(),
                  color: Colors.purpleAccent,
                  tail: true,
                  delivered: true,
              ),
              BubbleNormalAudio(
                color: Color(0xFFE8E8EE),
                duration: duration.inSeconds.toDouble(),
                position: position.inSeconds.toDouble(),
                isPlaying: isPlaying,
                isLoading: isLoading,
                isPause: isPause,
                onSeekChanged: _changeSeek,
                onPlayPauseButtonClick: _playAudio,
                sent: true,
              ),
              BubbleNormal(
                text: 'bubble normal with tail',
                isSender: false,
                color: Color(0xFF1B97F3),
                tail: true,
                textStyle: TextStyle(
                  fontSize: 20,
                  color: Colors.white,
                ),
              ),
              BubbleNormal(
                text: 'bubble normal with tail',
                isSender: true,
                color: Color(0xFFE8E8EE),
                tail: true,
                sent: true,
              ),
              DateChip(
                date: new DateTime(now.year, now.month, now.day - 2),
              ),
              BubbleNormal(
                text: 'bubble normal without tail',
                isSender: false,
                color: Color(0xFF1B97F3),
                tail: false,
                textStyle: TextStyle(
                  fontSize: 20,
                  color: Colors.white,
                ),
              ),
              BubbleNormal(
                text: 'bubble normal without tail',
                color: Color(0xFFE8E8EE),
                tail: false,
                sent: true,
                seen: true,
                delivered: true,
              ),
              BubbleSpecialOne(
                text: 'bubble special one with tail',
                isSender: false,
                color: Color(0xFF1B97F3),
                textStyle: TextStyle(
                  fontSize: 20,
                  color: Colors.white,
                ),
              ),
              DateChip(
                date: new DateTime(now.year, now.month, now.day - 1),
              ),
              BubbleSpecialOne(
                text: 'bubble special one with tail',
                color: Color(0xFFE8E8EE),
                seen: true,
              ),
              BubbleSpecialOne(
                text: 'bubble special one without tail',
                isSender: false,
                tail: false,
                color: Color(0xFF1B97F3),
                textStyle: TextStyle(
                  fontSize: 20,
                  color: Colors.black,
                ),
              ),
              BubbleSpecialOne(
                text: 'bubble special one without tail',
                tail: false,
                color: Color(0xFFE8E8EE),
                sent: true,
              ),
              BubbleSpecialTwo(
                text: 'bubble special tow with tail',
                isSender: false,
                color: Color(0xFF1B97F3),
                textStyle: TextStyle(
                  fontSize: 20,
                  color: Colors.black,
                ),
              ),
              DateChip(
                date: now,
              ),
              BubbleSpecialTwo(
                text: 'bubble special tow with tail',
                isSender: true,
                color: Color(0xFFE8E8EE),
                sent: true,
              ),
              BubbleSpecialTwo(
                text: 'bubble special tow without tail',
                isSender: false,
                tail: false,
                color: Color(0xFF1B97F3),
                textStyle: TextStyle(
                  fontSize: 20,
                  color: Colors.black,
                ),
              ),
              BubbleSpecialTwo(
                text: 'bubble special tow without tail',
                tail: false,
                color: Color(0xFFE8E8EE),
                delivered: true,
              ),
              BubbleSpecialThree(
                text: 'bubble special three without tail',
                color: Color(0xFF1B97F3),
                tail: false,
                textStyle: TextStyle(color: Colors.white, fontSize: 16),
              ),
              BubbleSpecialThree(
                text: 'bubble special three with tail',
                color: Color(0xFF1B97F3),
                tail: true,
                textStyle: TextStyle(color: Colors.white, fontSize: 16),
              ),
              BubbleSpecialThree(
                text: "bubble special three without tail",
                color: Color(0xFFE8E8EE),
                tail: false,
                isSender: false,
              ),
              BubbleSpecialThree(
                text: "bubble special three with tail",
                color: Color(0xFFE8E8EE),
                tail: true,
                isSender: false,
              ),
              SizedBox(
                height: 100,
              )
            ],
          ),
        ),
        MessageBar(
          onSend: (_) => print(_),
          actions: [
            InkWell(
              child: Icon(
                Icons.add,
                color: Colors.black,
                size: 24,
              ),
              onTap: () {},
            ),
            Padding(
              padding: EdgeInsets.only(left: 8, right: 8),
              child: InkWell(
                child: Icon(
                  Icons.camera_alt,
                  color: Colors.green,
                  size: 24,
                ),
                onTap: () {},
              ),
            ),
          ],
        ),
      ],
    ),
    // This trailing comma makes auto-formatting nicer for build methods.
  );
}

New in v1.9.0

Message Status Enhancements

All bubble widgets now accept timestamp, isEdited, isForwarded, and messageId parameters.

BubbleNormal(
  text: 'This message was edited',
  isSender: true,
  color: Color(0xFFE8E8EE),
  tail: true,
  seen: true,
  timestamp: '12:34 PM',
  isEdited: true,
),

BubbleNormal(
  text: 'Forwarded from another chat',
  isSender: false,
  color: Color(0xFF1B97F3),
  tail: true,
  textStyle: TextStyle(color: Colors.white, fontSize: 16),
  isForwarded: true,
  timestamp: '12:35 PM',
),
Swipe Actions

Wrap any bubble with SwipeableBubble to add swipe-to-reply and swipe-to-delete gestures.

SwipeableBubble(
  onSwipeRight: () => print('Reply triggered'),
  onSwipeLeft: () => print('Delete triggered'),
  swipeThreshold: 64.0,
  enableHaptics: true,
  child: BubbleNormal(
    text: 'Swipe me!',
    isSender: false,
    color: Color(0xFF1B97F3),
    tail: true,
    textStyle: TextStyle(color: Colors.white, fontSize: 16),
  ),
),
Parameter Type Default Description
child Widget required The bubble to wrap
onSwipeRight VoidCallback? null Called on swipe-right (reply)
onSwipeLeft VoidCallback? null Called on swipe-left (delete)
swipeThreshold double 64.0 Drag distance to trigger
enableHaptics bool true Haptic feedback on threshold
rightActionColor Color Colors.blue Background for right swipe
leftActionColor Color Colors.red Background for left swipe
rightActionIcon Icon Icons.reply Icon for right swipe
leftActionIcon Icon Icons.delete Icon for left swipe
Message Groups / Clustering

Use BubbleGroupBuilder to automatically group consecutive messages from the same sender. Only the last message in each group shows a tail.

final messages = ['Hello!', 'How are you?', 'Good, thanks!'];
final senders  = ['alice', 'alice', 'bob'];

BubbleGroupBuilder(
  itemCount: messages.length,
  senderIdOf: (i) => senders[i],
  itemBuilder: (context, i, info) => BubbleNormal(
    text: messages[i],
    isSender: senders[i] == 'bob',
    tail: info.showTail,
    color: senders[i] == 'bob'
        ? const Color(0xFFE8E8EE)
        : const Color(0xFF1B97F3),
  ),
)

For advanced control, use MessageGroupHelper.compute() directly to get GroupInfo for each message:

final List<GroupInfo> groups = MessageGroupHelper.compute(
  senderIds: senders,
  timestamps: timestamps,  // optional
  threshold: const Duration(minutes: 2),
);

// groups[i].showTail  — whether to show the tail
// groups[i].showAvatar — whether to show the avatar
// groups[i].isGroupStart — first message in group (add extra top spacing)
Voice Message Waveform

BubbleNormalAudio now supports waveform visualization and playback speed control.

BubbleNormalAudio(
  color: Color(0xFFE8E8EE),
  duration: duration.inSeconds.toDouble(),
  position: position.inSeconds.toDouble(),
  isPlaying: isPlaying,
  isLoading: isLoading,
  isPause: isPause,
  onSeekChanged: _changeSeek,
  onPlayPauseButtonClick: _playAudio,
  sent: true,
  // Waveform visualization
  waveformData: [0.2, 0.8, 0.5, 0.9, 0.4, 0.7, 0.6, 0.3, 0.8, 0.5],
  waveformActiveColor: Color(0xFF1B97F3),
  waveformInactiveColor: Colors.grey,
  // Playback speed
  showPlaybackSpeed: true,
  playbackSpeed: _playbackSpeed,
  onPlaybackSpeedChanged: (speed) => setState(() => _playbackSpeed = speed),
),

waveformData is a List<double> of normalized bar heights (0.0–1.0). Tapping or dragging on the waveform seeks to that position. The speed button cycles 1x → 1.5x → 2x → 1x.

Issues

Please feel free to let me know any issue regarding to this plugin.

所属分类

依赖

依赖清单 1 项

源码仓库

使用此插件的项目