如何用flutter link previewer在Flutter的TextFormField实现WhatsApp风格链接预览?
在Flutter TextFormField中实现WhatsApp式链接预览并获取元数据
如果flutter_link_previewer包无法正常工作,可以通过手动解析网页的OG标签(或 fallback 到页面基础元数据)来获取链接的标题、图片和描述,同时实现类似WhatsApp的预览效果,步骤如下:
1. 添加依赖
在pubspec.yaml中添加所需依赖:
dependencies: flutter: sdk: flutter http: ^1.1.0 html: ^0.15.4
执行flutter pub get完成依赖安装。
2. 定义预览数据模型
创建类存储解析后的链接元数据:
class LinkPreviewData { final String? title; final String? imageUrl; final String? description; final String url; LinkPreviewData({ this.title, this.imageUrl, this.description, required this.url, }); }
3. 实现链接元数据解析函数
通过HTTP请求目标页面,优先解析OG标签,若不存在则使用页面基础元数据:
import 'package:http/http.dart' as http; import 'package:html/parser.dart' as parser; import 'package:html/dom.dart' as dom; Future<LinkPreviewData?> fetchLinkPreview(String url) async { try { final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final dom.Document document = parser.parse(response.body); final List<dom.Element> metaTags = document.querySelectorAll('meta'); String? title; String? imageUrl; String? description; // 解析OG标签 for (var tag in metaTags) { final String? property = tag.attributes['property']; final String? content = tag.attributes['content']; if (property == 'og:title') { title = content; } else if (property == 'og:image') { imageUrl = content; } else if (property == 'og:description') { description = content; } } // 若OG标签不存在,使用页面基础信息 title ??= document.querySelector('title')?.text; description ??= document.querySelector('meta[name="description"]')?.attributes['content']; return LinkPreviewData( title: title, imageUrl: imageUrl, description: description, url: url, ); } } catch (e) { print('解析链接预览失败: $e'); } return null; }
4. 在页面中集成预览功能
监听TextFormField输入,检测到链接后自动解析并展示预览卡片:
import 'package:flutter/material.dart'; class LinkPreviewScreen extends StatefulWidget { @override _LinkPreviewScreenState createState() => _LinkPreviewScreenState(); } class _LinkPreviewScreenState extends State<LinkPreviewScreen> { final TextEditingController _controller = TextEditingController(); LinkPreviewData? _previewData; // 匹配HTTP/HTTPS链接的正则 final RegExp _urlRegex = RegExp(r'https?://\S+'); void _checkForLink() { final String text = _controller.text; final Match? match = _urlRegex.firstMatch(text); if (match != null) { final String url = match.group(0)!; fetchLinkPreview(url).then((LinkPreviewData? data) { setState(() { _previewData = data; }); }); } else { setState(() { _previewData = null; }); } } @override void initState() { super.initState(); // 监听输入框文本变化 _controller.addListener(_checkForLink); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('WhatsApp式链接预览')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextFormField( controller: _controller, maxLines: null, decoration: const InputDecoration( hintText: '输入带链接的文本...', border: OutlineInputBorder(), ), ), // 展示预览卡片 if (_previewData != null) Container( margin: const EdgeInsets.only(top: 16.0), padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8.0), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 预览图片 if (_previewData!.imageUrl != null) Container( width: 80, height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4.0), image: DecorationImage( image: NetworkImage(_previewData!.imageUrl!), fit: BoxFit.cover, onError: (_, __) => const Icon(Icons.broken_image), ), ), ), const SizedBox(width: 12.0), // 预览文本内容 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (_previewData!.title != null) Text( _previewData!.title!, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16.0, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), if (_previewData!.description != null) Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text( _previewData!.description!, style: TextStyle(color: Colors.grey.shade600), maxLines: 3, overflow: TextOverflow.ellipsis, ), ), Text( _previewData!.url, style: TextStyle(color: Colors.grey.shade500, fontSize: 12.0), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ), ], ), ), ); } }
注意事项
- 网络权限:在Android的
AndroidManifest.xml中添加<uses-permission android:name="android.permission.INTERNET" />;若需访问HTTP链接,iOS需在Info.plist中配置NSAppTransportSecurity例外。 - 性能优化:可添加输入防抖逻辑,避免用户输入时频繁发起网络请求。
- 错误处理:可进一步完善图片加载失败、链接不可访问等场景的UI提示。
内容的提问来源于stack exchange,提问作者krishna veni
相关产品推荐
相关产品推荐

