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

如何在Flutter中实现类似WhatsApp的聊天气泡?

求助:Flutter实现WhatsApp式聊天气泡(提取URL消息中的网页预览图)

我正在开发类似WhatsApp的聊天气泡组件,目前需要实现从含URL的消息中提取并展示网页预览图的功能。例如给定新闻URL,希望能像WhatsApp那样显示网页标题+预览图+链接的组合样式。

预期效果

预期效果1
预期效果2

实际效果

实际效果1
实际效果2

当前代码

Row(
  crossAxisAlignment: CrossAxisAlignment.end,
  mainAxisSize: MainAxisSize.max,
  mainAxisAlignment: MainAxisAlignment.start,
  children: [
    Container(
      width: MediaQuery.of(context).size.width * 0.75,
      padding: const EdgeInsets.all(5.0),
      decoration: const BoxDecoration(
        color: Colors.amber,
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(15.0),
          topRight: Radius.circular(15.0),
          bottomLeft: Radius.circular(0.0),
          bottomRight: Radius.circular(15.0),
        ),
      ),
      child: Column(
        children: [
          Align(
            alignment: Alignment.centerLeft,
            child: Text(
              name,
              style: const TextStyle(
                color: Color.fromARGB(255, 50, 150, 150),
                fontSize: 12.5,
              ),
            ),
          ),
          Align(
            alignment: Alignment.centerLeft,
            child: Linkify(
              onOpen: (link) async {
                if (await canLaunchUrl(
                  Uri.parse(link.url),
                )) {
                  await launchUrl(
                      Uri.parse(link.url));
                } else {
                  throw "Could not launch $link";
                }
              },
              text: message,
              style: const TextStyle(
                color: Color.fromARGB(255, 50, 150, 150),
                fontSize: 20.0,
              ),
              linkStyle: const TextStyle(
                fontSize: 20.0,
                color: Colors.blue,
                decoration: TextDecoration.underline,
              ),
            ),
          ),
          Align(
            alignment: Alignment.centerRight,
            child: Text(
              timeago.format(sentAt.toDate()),
              style: const TextStyle(
                color: Color.fromARGB(255, 50, 150, 150),
                fontSize: 15.0,
              ),
            ),
          ),
        ],
      ),
    ),
  ],
),

实现方案建议

要实现网页链接预览,核心是解析URL对应的网页元数据(标题、预览图),再调整布局展示预览卡片。

方法一:使用第三方链接预览包(快速实现)

推荐使用link_preview_generator包,它会自动解析网页的Open Graph标签,生成类似WhatsApp的预览卡片。

  1. 在pubspec.yaml添加依赖:
dependencies:
  link_preview_generator: ^any
  1. 修改核心布局代码:
Row(
  crossAxisAlignment: CrossAxisAlignment.end,
  mainAxisSize: MainAxisSize.max,
  mainAxisAlignment: MainAxisAlignment.start,
  children: [
    Container(
      width: MediaQuery.of(context).size.width * 0.75,
      padding: const EdgeInsets.all(5.0),
      decoration: const BoxDecoration(
        color: Colors.amber,
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(15.0),
          topRight: Radius.circular(15.0),
          bottomLeft: Radius.circular(0.0),
          bottomRight: Radius.circular(15.0),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            name,
            style: const TextStyle(
              color: Color.fromARGB(255, 50, 150, 150),
              fontSize: 12.5,
            ),
          ),
          // 判断消息中是否包含URL,显示预览或原文本
          if (message.contains(RegExp(r'https?://\S+')))
            // 提取消息中的第一个URL
            LinkPreviewGenerator(
              link: RegExp(r'https?://\S+').stringMatch(message)!,
              onTap: (url) async {
                if (await canLaunchUrl(Uri.parse(url))) {
                  await launchUrl(Uri.parse(url));
                }
              },
              borderRadius: 8.0,
              boxShadow: const [BoxShadow(color: Colors.grey, blurRadius: 2)],
              previewHeight: 120,
            )
          else
            Text(
              message,
              style: const TextStyle(
                color: Color.fromARGB(255, 50, 150, 150),
                fontSize: 20.0,
              ),
            ),
          Align(
            alignment: Alignment.centerRight,
            child: Text(
              timeago.format(sentAt.toDate()),
              style: const TextStyle(
                color: Color.fromARGB(255, 50, 150, 150),
                fontSize: 15.0,
              ),
            ),
          ),
        ],
      ),
    ),
  ],
),

方法二:自定义解析网页元数据(无第三方依赖)

如果不想用第三方包,可通过http+html包手动解析网页的og标签:

  1. 添加依赖:
dependencies:
  http: ^any
  html: ^any
  1. 实现解析函数:
Future<Map<String, String?>> fetchWebPreview(String url) async {
  try {
    final response = await http.get(Uri.parse(url));
    final document = parse(response.body);
    final ogTitle = document.querySelector('meta[property="og:title"]')?.attributes['content'];
    final ogImage = document.querySelector('meta[property="og:image"]')?.attributes['content'];
    return {'title': ogTitle, 'image': ogImage};
  } catch (e) {
    return {'title': url, 'image': null};
  }
}
  1. 在组件中使用FutureBuilder展示预览:
// 假设已提前提取URL
final String url = RegExp(r'https?://\S+').stringMatch(message)!;

// 替换原Linkify部分为:
FutureBuilder<Map<String, String?>>(
  future: fetchWebPreview(url),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();
    }
    final previewData = snapshot.data!;
    return GestureDetector(
      onTap: () async {
        if (await canLaunchUrl(Uri.parse(url))) {
          await launchUrl(Uri.parse(url));
        }
      },
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 8),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: Colors.grey.shade300),
        ),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (previewData['image'] != null)
              Expanded(
                flex: 1,
                child: Image.network(
                  previewData['image']!,
                  fit: BoxFit.cover,
                  height: 100,
                ),
              ),
            Expanded(
              flex: 2,
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      previewData['title'] ?? url,
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      Uri.parse(url).host,
                      style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  },
)

注意事项

  • 处理网络请求异常:当网页无法访问或无og标签时,fallback显示原URL文本。
  • 优化性能:缓存已解析的网页预览数据,避免重复请求。
  • 权限配置:确保Android/iOS的网络权限已开启(Android的INTERNET权限,iOS的NSAppTransportSecurity配置)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 21:20:20