Flutter使用screenshot进行截屏和截长图以及分享保存的全流程指南

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:通过父容器的 widthheight 约束 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();有惊喜哦~

往期文章

相关推荐
xiangxiongfly9151 分钟前
Android ViewRootImpl源码分析
android·绘制流程·viewrootimpl·activitythread
踩着两条虫2 分钟前
VTJ.PRO 在线应用开发平台的DSL生命周期
前端·低代码·ai编程
我是伪码农2 分钟前
JS 复习
开发语言·前端·javascript
小碗细面2 分钟前
Claude Code 很强,但为什么我越来越常打开 Codex App?
前端·chatgpt·ai编程
愿你如愿3 分钟前
React Fiber 的主要目标是什么
前端·react.js
漂移的电子8 分钟前
【echarts 细节】
前端·javascript·echarts
im_AMBER10 分钟前
万字长文:从零实现 Yjs + Hocuspocus 协同文档
前端·react.js·前端框架
kyriewen10 分钟前
事件流与事件委托:当点击按钮时,浏览器里发生了什么?
前端·javascript·面试
是真的小外套12 分钟前
第十一章:Flask入门之从零构建Python Web应用
前端·python·flask