Flutter 三方库 share_plus 的鸿蒙化适配与实战指南
Flutter 三方库 share_plus 的鸿蒙适配与实战指南 本文介绍了 Flutter 插件 share_plus 在鸿蒙系统的应用实践。该插件提供跨平台分享功能,支持文本、链接、图片、文件等多种内容的系统级分享。文章详细展示了如何封装分享服务类,包含分享文本、链接、聊天消息、文件和图片等核心方法实现,并提供了在聊天应用中集成分享功能的代码示例。特别说明该插件在鸿蒙系统上通过调用原生分享
Flutter 三方库 share_plus 的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
Hey 大家好!上海某高校计算机专业大一学生 📤!今天来聊聊分享功能!
做聊天 App 怎么能少了分享呢?用户看到一条有趣的消息、一张好看的图片,想分享给好友——这时候就需要系统级的分享功能!share_plus 就是来解决这个问题的!
一、share_plus 是什么?
share_plus 是一个 Flutter 插件,提供跨平台的分享功能。它可以调用系统的分享面板,让用户选择分享到微信、QQ、邮件等任何支持接收分享的应用。
特点:
- ✅ 跨平台:iOS、Android、鸿蒙、Web 都能用
- ✅ 系统原生分享面板
- ✅ 支持文本、链接、图片、文件
- ✅ 零 UI,直接调用系统功能
二、依赖配置
dependencies:
share_plus: ^10.1.4
AtomGit 适配说明:share_plus 底层调用各平台的原生分享 API,在鸿蒙上依赖系统的分享服务,兼容性良好!
三、封装分享服务
import 'package:flutter/foundation.dart';
import 'package:share_plus/share_plus.dart';
/// 分享服务
/// 支持分享文本、链接、图片、文件到其他App
class ShareService {
static ShareService? _instance;
static ShareService get instance => _instance ??= ShareService._();
ShareService._();
1. 分享文本
/// 分享纯文本【基础方法】
Future<bool> shareText(String text, {String? subject}) async {
try {
await Share.share(text, subject: subject);
debugPrint('分享文本成功');
return true;
} catch (e) {
debugPrint('分享文本失败: $e');
return false;
}
}
2. 分享链接
/// 分享链接【实用方法】
Future<bool> shareUrl(String url, {String? text}) async {
try {
final shareText = text != null ? '$text\n$url' : url;
await Share.share(shareText);
debugPrint('分享链接成功: $url');
return true;
} catch (e) {
debugPrint('分享链接失败: $e');
return false;
}
}
3. 分享聊天消息
/// 分享聊天消息【聊天场景专用】
Future<bool> shareChatMessage({
required String senderName,
required String content,
required String time,
}) async {
try {
final text = '$senderName ($time):\n$content';
await Share.share(text);
debugPrint('分享聊天消息成功');
return true;
} catch (e) {
debugPrint('分享聊天消息失败: $e');
return false;
}
}
4. 分享文件
/// 分享文件【重要方法】
Future<bool> shareFiles(List<String> filePaths, {String? text}) async {
try {
// 将文件路径转换为 XFile 对象
final xFiles = filePaths.map((path) => XFile(path)).toList();
await Share.shareXFiles(xFiles, text: text);
debugPrint('分享文件成功: ${filePaths.length}个文件');
return true;
} catch (e) {
debugPrint('分享文件失败: $e');
return false;
}
}
5. 分享图片
/// 分享单张图片【常用方法】
Future<bool> shareImage(String imagePath, {String? text}) async {
try {
await Share.shareXFiles(
[XFile(imagePath)],
text: text,
);
debugPrint('分享图片成功');
return true;
} catch (e) {
debugPrint('分享图片失败: $e');
return false;
}
}
/// 分享多张图片【实用方法】
Future<bool> shareImages(List<String> imagePaths, {String? text}) async {
try {
final xFiles = imagePaths.map((path) => XFile(path)).toList();
await Share.shareXFiles(xFiles, text: text);
debugPrint('分享多张图片成功: ${imagePaths.length}张');
return true;
} catch (e) {
debugPrint('分享多张图片失败: $e');
return false;
}
}
6. 分享带截图
/// 分享文字+图片(用于分享卡片预览)
Future<bool> shareWithPreview({
required String title,
required String text,
required String imagePath,
}) async {
try {
await Share.shareXFiles(
[XFile(imagePath)],
text: '$title\n$text',
subject: title,
);
return true;
} catch (e) {
debugPrint('分享失败: $e');
return false;
}
}
四、在聊天页面中集成
class ChatMessageBubble extends StatelessWidget {
final ChatMessage message;
final VoidCallback onReply;
final VoidCallback onForward;
final VoidCallback onCopy;
final VoidCallback onDelete;
const ChatMessageBubble({
super.key,
required this.message,
required this.onReply,
required this.onForward,
required this.onCopy,
required this.onDelete,
});
Widget build(BuildContext context) {
return GestureDetector(
onLongPress: () => _showMessageMenu(context),
child: // 消息气泡内容
);
}
/// 长按显示操作菜单
void _showMessageMenu(BuildContext context) {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (context) => Container(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 【分享按钮】
ListTile(
leading: const Icon(Icons.share, color: Color(0xFF6366F1)),
title: const Text('分享'),
onTap: () {
Navigator.pop(context);
_shareMessage(context);
},
),
ListTile(
leading: const Icon(Icons.reply),
title: const Text('回复'),
onTap: () {
Navigator.pop(context);
onReply();
},
),
ListTile(
leading: const Icon(Icons.copy),
title: const Text('复制'),
onTap: () {
Navigator.pop(context);
_copyMessage();
},
),
ListTile(
leading: const Icon(Icons.delete, color: Colors.red),
title: const Text('删除', style: TextStyle(color: Colors.red)),
onTap: () {
Navigator.pop(context);
_confirmDelete(context);
},
),
],
),
),
);
}
/// 分享消息
void _shareMessage(BuildContext context) async {
final shareService = ShareService.instance;
if (message.type == MessageType.text) {
// 分享文本消息
await shareService.shareChatMessage(
senderName: message.senderName,
content: message.content,
time: _formatTime(message.timestamp),
);
} else if (message.type == MessageType.image && message.imagePath != null) {
// 分享图片
await shareService.shareImage(
message.imagePath!,
text: '来自 ${message.senderName} 的图片',
);
}
}
/// 复制消息
void _copyMessage() {
if (message.type == MessageType.text) {
Clipboard.setData(ClipboardData(text: message.content));
}
onCopy();
}
/// 确认删除
void _confirmDelete(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: const Text('确定要删除这条消息吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
onDelete();
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('删除'),
),
],
),
);
}
String _formatTime(DateTime time) {
return '${time.hour}:${time.minute.toString().padLeft(2, '0')}';
}
}
五、在商品详情页中分享
/// 商品卡片分享
class ProductShareButton extends StatelessWidget {
final Product product;
const ProductShareButton({super.key, required this.product});
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.share),
onPressed: () => _shareProduct(context),
);
}
void _shareProduct(BuildContext context) async {
final shareService = ShareService.instance;
// 分享商品链接
final url = 'https://example.com/product/${product.id}';
final text = '''
${product.name}
价格: ¥${product.price}
${product.description}
''';
await shareService.shareUrl(url, text: text);
}
}
六、测试功能
/// 测试分享功能
Future<Map<String, dynamic>> testShare() async {
try {
await Share.share(
'这是一条来自Flutter聊天App的测试分享!\n\n功能包括:\n- 文本分享\n- 链接分享\n- 图片分享\n- 文件分享',
subject: 'Flutter聊天App分享测试',
);
return {'success': true, 'message': '分享功能正常'};
} catch (e) {
return {'success': false, 'message': '分享功能异常: $e'};
}
}
七、踩坑纪实
踩坑1:分享图片闪退 💥
在鸿蒙设备上分享图片时,有时候会闪退!原因是图片路径可能不存在或者格式不支持。解决方案:
// 分享前检查文件是否存在
final file = File(imagePath);
if (!await file.exists()) {
return {'success': false, 'message': '图片文件不存在'};
}
// 如果是网络图片,先下载到本地
final localPath = await _downloadImage(imageUrl);
if (localPath != null) {
await Share.shareXFiles([XFile(localPath)]);
}
踩坑2:分享文本过长被截断 📝
分享超长文本时,部分应用会截断。后来改成限制文本长度:
String truncateText(String text, int maxLength) {
if (text.length <= maxLength) return text;
return '${text.substring(0, maxLength - 3)}...';
}
踩坑3:XFile 路径格式问题 🔗
在鸿蒙上,XFile 对路径格式有要求。必须使用完整的文件路径,不能用 content:// 格式:
// 错误 ❌
// XFile('content://xxx')
// 正确 ✅
XFile('/data/user/0/xxx/cache/image.jpg')
踩坑4:没有安装可分享的应用 📱
如果用户手机上没有安装任何可接收分享的应用,share_plus 会抛出异常。需要做好异常处理:
try {
await Share.share('Hello');
} on PlatformException catch (e) {
if (e.code == 'ACTIVITY_NOT_FOUND') {
// 没有可用的分享应用
showSnackBar('未找到可用的分享应用');
}
}
八、Android 配置
<!-- AndroidManifest.xml -->
<application>
<!-- 如果需要分享文件,需要添加 FileProvider -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
<!-- res/xml/file_paths.xml -->
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache" path="/"/>
<files-path name="files" path="/"/>
<external-path name="external" path="/"/>
</paths>
九、效果展示

功能验证结果:
- ✅ 分享图片正常
- ✅ 分享文件正常
十、总结心得
share_plus 真的是"五星级好用"的库!一行代码就能调用系统分享,而且鸿蒙兼容性也很好。
核心要点:
- 文件路径要完整且存在
- 网络图片需要先下载到本地
- 长文本要适当截断
- 做好异常处理
使用场景:
- 聊天消息分享
- 商品链接分享
- 文章分享
- 朋友圈分享
后续计划:
- 研究 deep link 实现分享回调
- 尝试自定义分享面板
今天的分享就到这里!有问题评论区见!
更多推荐




所有评论(0)