1. 前言
最近在项目里做了截屏和长图分享功能,所以结合自己的使用经历,写一个教程和总结。本文基于 screenshot: ^2.1.0 版本编写,适配 Flutter 3.0+,支持 iOS、Android、Windows和 Web 平台。可捕获屏幕上可见/不可见的 Widget,满足截图、长图、保存、分享等场景需求。
官方文档:github.com/SachinGanes...
2. 插件核心 API
| 类/方法 | 类型 | 参数 | 说明 |
|---|---|---|---|
| Screenshot | Widget | controller: ScreenshotControllerchild: Widget | 包裹需要截图的 Widget,为截图提供渲染边界 |
| ScreenshotController | 控制器 | - | 截图调度核心实例,负责触发截图、处理截图结果 |
| capture() | 方法 | pixelRatio: double(可选)delay: Duration(可选) | 捕获已渲染 Widget,返回 Uint8List 类型图片数据 |
| captureFromWidget() | 方法 | Widget(必传)、pixelRatio(可选)、delay(可选) | 捕获未渲染(不可见)Widget,无需包裹在 Screenshot 内 |
| captureFromLongWidget() | 方法 | Widget、context、delay、constraints(可选) | 捕获超长不可见列表 Widget,需传入上下文和约束 |
| captureAndSave() | 方法 | path(必传)、fileName(可选)、pixelRatio(可选) | 捕获并保存到指定路径(Web 不支持该方法) |
3. 基础使用
下面是分别是依赖项引入和最基础的截图示例:
3.1. 引入依赖
xml
dependencies:
screenshot: ^3.0.0
3.2. 创建控制器与包裹 Widget
dart
final ScreenshotController _controller = ScreenshotController();
Screenshot(
controller: _controller,
child: const Text("Flutter Screenshot Demo"),
)
3.3. 执行截图
dart
Uint8List? _image;
_controller.capture(
pixelRatio: 1.5,
delay: const Duration(milliseconds: 10),
).then((value) {
setState(() => _image = value);
}).catchError((e) {
debugPrint(e.toString());
});
4. 高级功能
4.1. 捕获不可见的 Widget
dart
_controller.captureFromWidget(
Container(
padding: const EdgeInsets.all(20),
color: Colors.blue,
child: const Text("Invisible Widget"),
),
).then((value) {
// 处理图片(如保存、展示)
});
4.2. 捕获超长的列表 Widget
dart
final _longWidget = Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(50, (i) => Text("Item $i")),
);
_controller.captureFromLongWidget(
InheritedTheme.captureAll(context, Material(child: _longWidget)),
context: context,
delay: const Duration(milliseconds: 100),
).then((value) {
// 处理长图
});
4.3. 保存到指定路径
dart
import 'package:path_provider/path_provider.dart';
final dir = await getApplicationDocumentsDirectory();
await _controller.captureAndSave(
dir.path,
fileName: "screenshot_${DateTime.now().millisecondsSinceEpoch}.png",
);
4. 截图的像素与大小控制
截图的像素清晰度和尺寸大小,主要通过以下两种方式控制,适配不同场景需求:
4.1. 像素调整(控制清晰度)
通过 capture()、captureFromWidget() 等方法的 pixelRatio 参数控制,该参数表示"像素密度比",默认值为 1.0。
- pixelRatio = 1.0:默认清晰度,适配设备基础像素,文件体积较小;
- pixelRatio = 1.5~2.0:常用清晰值,兼顾清晰度和文件体积,适合大多数场景;
- pixelRatio ≥ 3.0:高清模式,适合需要放大查看的场景(如海报、凭证),但文件体积会显著增大。
dart
// 高清截图示例(pixelRatio设为2.0)
Uint8List? _highDefImage = await _controller.capture(pixelRatio: 2.0);
4.2. 大小控制(控制图片尺寸)
分两种场景控制,核心是约束目标 Widget 的尺寸,截图会自动适配 Widget 实际渲染尺寸:
- 可见 Widget:通过父容器的
width、height约束 Screenshot 组件的尺寸,截图尺寸与约束尺寸一致; - 不可见 Widget:在 captureFromWidget() 中,给目标 Widget 包裹 Container 并设置固定宽高,或通过 constraints 参数指定尺寸。
dart
// 控制不可见Widget截图尺寸(固定宽高300x200)
_controller.captureFromWidget(
Container(
width: 300,
height: 200,
child: const Text("Fixed Size Widget"),
),
);
5. 实现原理
screenshot 插件的核心实现依赖 Flutter 渲染机制中的 RenderRepaintBoundary 组件。该组件会为其包裹的 Widget 创建独立的渲染图层,避免与其他 Widget 重复渲染,同时允许单独捕获该图层的像素数据。
其流程为:通过 Screenshot 组件给目标 Widget 绑定 RenderRepaintBoundary;ScreenshotController 调用截图方法时,会获取该渲染边界的 RenderObject;通过 RenderObject 的 toImage() 方法将渲染结果转为 Image 对象;再将 Image 编码为 Uint8List(图片字节流),最终返回给开发者用于后续处理(保存、分享等)。
对于不可见/长图捕获,插件通过手动创建渲染上下文,强制 Widget 完成渲染后再执行截图操作,突破了"仅能捕获可见 Widget"的限制。
6. 保存到系统相册
该功能使用 gal 插件实现相册保存。gal 插件适配性强,支持多平台,当前使用稳定版本 gal: ^2.1.0。
PS:也可以使用image_gallery_saver插件,有啥用啥。
6.1. 引入依赖
xml
dependencies:
screenshot: ^3.0.0
gal: ^2.1.0
permission_handler: ^10.2.0 # 用于申请存储权限
6.2. 权限配置
Android:在 AndroidManifest.xml 中添加存储权限
xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30"/>
iOS:在 Info.plist 中添加相册权限
xml
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问相册以保存截图</string>
6.3. 保存代码实现
dart
import 'package:gal/gal.dart';
import 'package:permission_handler/permission_handler.dart';
// 截图并保存到相册
void saveScreenshotToGallery() async {
// 申请存储/相册权限
var status = await Permission.storage.request();
if (!status.isGranted) {
debugPrint("权限申请失败,无法保存相册");
return;
}
// 执行截图
Uint8List? image = await _controller.capture(pixelRatio: 1.5);
if (image == null) {
debugPrint("截图失败");
return;
}
// 调用gal插件保存到相册
await Gal.putImageBytes(image, album: "Flutter截图");
debugPrint("保存相册成功");
}
7. 分享图片
使用 share_plus: ^7.2.2插件实现截图分享,支持多平台(iOS、Android、Web 等),可分享图片给其他应用。
7.1. 引入依赖
xml
dependencies:
screenshot: ^3.0.0
share_plus: ^7.2.2
path_provider: ^2.1.1 # 用于临时存储图片
7.2. 分享代码实现
dart
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
// 截图并分享
void shareScreenshot() async {
// 执行截图
Uint8List? image = await _controller.capture(pixelRatio: 1.5);
if (image == null) {
debugPrint("截图失败,无法分享");
return;
}
// 保存图片到临时目录(分享需要本地文件路径)
final tempDir = await getTemporaryDirectory();
final tempFile = File("${tempDir.path}/screenshot_share.png");
await tempFile.writeAsBytes(image);
// 调用分享功能
await Share.shareXFiles(
[XFile(tempFile.path)],
text: "分享一张Flutter截图",
);
}
8. 注意事项
- 不支持 Platform View(如地图、相机、WebView),捕获此类组件会出现黑屏或空白。
- 图片模糊可提高
pixelRatio(建议 1.5--3.0),但会增加文件体积和截图耗时。 - 若出现截图黑屏/空白,大概率是 Widget 未渲染完成,添加
delay: Duration(milliseconds: 10--100)即可解决。 captureAndSave()不支持 Web 平台,Web 端需通过capture()获取字节流后,通过浏览器 API 下载。- 使用 gal 插件保存相册时,Android 11+ 需申请 MANAGE_EXTERNAL_STORAGE 权限,iOS 需配置相册权限描述。
9. 总结
screenshot 是 Flutter 稳定的 Widget 截图方案,基于 RenderRepaintBoundary 实现,支持可见/不可见/长图捕获。配合 gal 插件可实现相册保存,结合 share_plus 可完成分享,通过 pixelRatio 和尺寸约束可控制截图效果。使用时注意权限、渲染延迟与平台限制,可满足多数商业应用截图需求。
本文仅是简单案例,实际项目中有各种奇怪的需求,可以在评论区讨论,大家一起集思广益,分享解决方案。
本次分享就到这儿啦,我是鹏多多,深耕前端的技术创作者,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~
PS:在本页按F12,在console中输入document.getElementsByClassName('panel-btn')[0].click();有惊喜哦~
往期文章
- Flutter使用Gal展示和保存图片资源
- Flutter使用package_info_plus库获取应用信息的教程
- Flutter下拉刷新上拉加载侧拉刷新插件:easy_refresh全面使用指南
- Flutter-使用EventBus实现组件间数据通信
- Flutter输入框TextField的属性与实战用法全面解析+示例
- Flutter自定义日历table_calendar完全指南+案例
- Flutter-屏幕自适应插件flutter_screenutil教程全指南
- Flutter-使用url_launcher打开链接/应用/短信/邮件和评分跳转等
- Flutter图片选择库multi_image_picker_plus和image_picker的对比和使用解析
- 解锁Flutter弹窗新姿势:dialog-flutter_smart_dialog插件解读+案例
- Flutter-切换状态显示不同组件10种实现方案全解析
- Flutter-详解控制组件显示的两种方式Offstage与Visibility
- Flutter-使用AnimatedDefaultTextStyle实现文本动画
- Flutter-使用SafeArea组件处理各机型的安全距离
- Flutter-实现渐变色边框背景以及渐变色文字
- Flutter-使用confetti制作炫酷纸屑爆炸粒子动画