文章目录
- [1. 前言](#1. 前言)
- [2. 插件核心 API](#2. 插件核心 API)
- [3. 基础使用](#3. 基础使用)
-
- [3.1. 引入依赖](#3.1. 引入依赖)
- [3.2. 创建控制器与包裹 Widget](#3.2. 创建控制器与包裹 Widget)
- [3.3. 执行截图](#3.3. 执行截图)
- [4. 高级功能](#4. 高级功能)
-
- [4.1. 捕获不可见的 Widget](#4.1. 捕获不可见的 Widget)
- [4.2. 捕获超长的列表 Widget](#4.2. 捕获超长的列表 Widget)
- [4.3. 保存到指定路径](#4.3. 保存到指定路径)
- [4. 截图的像素与大小控制](#4. 截图的像素与大小控制)
-
- [4.1. 像素调整(控制清晰度)](#4.1. 像素调整(控制清晰度))
- [4.2. 大小控制(控制图片尺寸)](#4.2. 大小控制(控制图片尺寸))
- [5. 实现原理](#5. 实现原理)
- [6. 保存到系统相册](#6. 保存到系统相册)
-
- [6.1. 引入依赖](#6.1. 引入依赖)
- [6.2. 权限配置](#6.2. 权限配置)
- [6.3. 保存代码实现](#6.3. 保存代码实现)
- [7. 分享图片](#7. 分享图片)
-
- [7.1. 引入依赖](#7.1. 引入依赖)
- [7.2. 分享代码实现](#7.2. 分享代码实现)
- [8. 注意事项](#8. 注意事项)
- [9. 总结](#9. 总结)
1. 前言
最近在项目里做了截屏和长图分享功能,所以结合自己的使用经历,写一个教程和总结。本文基于 screenshot: ^3.0.0 版本编写,适配 Flutter 3.0+,支持 iOS、Android、Windows和 Web 平台。可捕获屏幕上可见/不可见的 Widget,满足截图、长图、保存、分享等场景需求。
官方文档:传送门
2. 插件核心 API
Screenshot组件,包裹需要截图的 Widget,为截图提供渲染边界,参数如下:
| 属性 | 类型 | 参数 | 说明 |
|---|---|---|---|
| controller | 控制器 | ScreenshotController | 截图调度核心实例,负责触发截图、处理截图结果 |
| child | 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. 引入依赖
yaml
dependencies:
screenshot: ^3.0.0
3.2. 创建控制器与包裹 Widget
dart
final ScreenshotController _myScreenshotController = ScreenshotController();
Screenshot(
controller: _myScreenshotController,
child: const Text("Flutter Screenshot Demo"),
)
3.3. 执行截图
dart
Uint8List? _image;
_myScreenshotController.capture(
pixelRatio: 1.5,
delay: const Duration(milliseconds: 10),
).then((value) {
setState(() => _image = value);
}).catchError((e) {
debugPrint(e.toString());
});
4. 高级功能
下面是一些高级功能的简易例子:
4.1. 捕获不可见的 Widget
dart
_myScreenshotController.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")),
);
_myScreenshotController.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 _myScreenshotController.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设为3.0
Uint8List? _highDefImage = await _myScreenshotController.capture(pixelRatio: 3.0);
4.2. 大小控制(控制图片尺寸)
分两种场景控制,核心是约束目标 Widget 的尺寸,截图会自动适配 Widget 实际渲染尺寸:
- 可见 Widget:通过父容器的
width、height约束 Screenshot 组件的尺寸,截图尺寸与约束尺寸一致; - 不可见 Widget:在 captureFromWidget() 中,给目标 Widget 包裹 Container 并设置固定宽高,或通过 constraints 参数指定尺寸。
dart
// 控制不可见Widget截图尺寸(固定宽高300x200)
_myScreenshotController.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.3.2。
PS:也可以使用image_gallery_saver插件,有啥用啥。
6.1. 引入依赖
yaml
dependencies:
screenshot: ^3.0.0
gal: ^2.3.2
# 用于申请存储权限
permission_handler: ^11.3.1
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 中添加相册权限
plist
<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 _myScreenshotController.capture(pixelRatio: 1.5);
if (image == null) {
debugPrint("截图失败");
return;
}
// 调用gal插件保存到相册
await Gal.putImageBytes(image, album: "Flutter截图");
debugPrint("保存相册成功");
}
7. 分享图片
使用 share_plus: ^12.0.1插件实现截图分享,支持多平台(iOS、Android、Web 等),可分享图片给其他应用。
7.1. 引入依赖
yaml
dependencies:
screenshot: ^3.0.0
# 分享
share_plus: ^12.0.1
# 查找文件系统中常用位置 用于临时存储图片
path_provider: ^2.1.4
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 _myScreenshotController.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.querySelectorAll('.tool-item-href')[0].click(),有惊喜哦~
往期文章
个人主页