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();有惊喜哦~

往期文章

相关推荐
Carson带你学Android6 小时前
OpenClaw移动端要来了?Android官宣AI原生支持App Functions
android
拉不动的猪6 小时前
重温Vue异步更新队列
前端·javascript·面试
黄林晴6 小时前
Android 删了 XML 预览,现在你必须学 Compose 了
android
Mike_jia6 小时前
OpenClaw:开源个人AI助手的“执行革命“
前端
摸鱼的春哥6 小时前
吃龙虾🦞咯!万字拆解OpenClaw的架构与设计
前端·javascript·后端
三少爷的鞋6 小时前
Android 面试系列 | 内存泄露:从"手动配对"到"架构自愈"
android
恋猫de小郭6 小时前
什么 AI 写 Android 最好用?官方做了一个基准测试排名
android·前端·flutter
anOnion15 小时前
构建无障碍组件之Switch Pattern
前端·html·交互设计
华洛16 小时前
多写点skill吧,写的越多这行业死的越快。
前端·javascript·产品