跳转到正文
YGJoon 技术博客

Flutter 实现 Widget 截图并保存到本地

发布于:
·874 字 · 约 2 分钟·作者:
给目标组件包一层截图边界并绑定全局键,一键把任意界面渲染成图片:可调清晰度倍率,支持保存到相册或直接分享
Flutter 实现 Widget 截图并保存到本地文章封面
目录

在 Flutter 中,将 Widget 转换为图片并保存或分享是一项常见需求。无论是为了保存当前屏幕,还是分享某个特定 UI 部分的截图,这些功能都可以通过 RepaintBoundary 实现。本文将详细介绍如何在 Flutter 中实现 Widget 截图并将图片保存到本地。

实现原理

RepaintBoundary 是 Flutter 提供的一个组件,它可以将某个 Widget 包裹在一个独立的绘制区域内,并允许你将该区域渲染为图片。核心步骤如下:

  1. 用 RepaintBoundary 包裹需要截图的 Widget。
  2. 使用 GlobalKey 获取 RepaintBoundary 的上下文。
  3. 调用 toImage() 方法将 Widget 转换为图片。
  4. 将图片转换为 Uint8List 格式,并保存到本地或分享。

具体实现步骤

包裹 Widget 并使用 GlobalKey

首先,将需要截图的部分用 RepaintBoundary 包裹,并通过 GlobalKey 获取对应的上下文。

RepaintBoundary(
  key: _globalKey,
  child: Container(
    color: Colors.blueAccent,
    padding: const EdgeInsets.all(16.0),
    child: const Text(
      '这是一个可以截图的 Widget',
      style: TextStyle(color: Colors.white, fontSize: 24.0),
    ),
  ),
)

将 Widget 转换为图片

使用 GlobalKey 获取 RepaintBoundary 的 RenderObject,然后将其渲染为图片,并转换为 Uint8List,以便后续的保存或分享操作。

/// 创建图片
  Future<Uint8List?> capturePng() async {
    try {
      RenderRepaintBoundary boundary =
          globalKey.currentContext?.findRenderObject() as RenderRepaintBoundary;
      ui.Image image = await boundary.toImage(pixelRatio: 3.0);
      ByteData? byteData =
          await image.toByteData(format: ui.ImageByteFormat.png);
      return byteData?.buffer.asUint8List();
    } catch (e) {
      return null;
    }
  }

保存图片到本地

使用 path_provider 插件访问设备的文件系统,并将图片保存到本地。首先在 pubspec.yaml 中添加依赖:

dependencies:
  path_provider: ^2.0.9
  image_gallery_saver: ^1.7.1
  permission_handler: ^10.2.0

接着,通过 path_provider 获取外部存储路径,并保存图片到该路径。

Future<void> saveImageToLocal(Uint8List imageBytes) async {
  final directory = await getExternalStorageDirectory();
  final imagePath = '${directory!.path}/screenshot.png';
  File imgFile = File(imagePath);
  await imgFile.writeAsBytes(imageBytes);
  print('图片已保存至: $imagePath');
}

或者使用 ImageGallerySaver 保存到图库,同时需要注意存储图片时权限问题

Android: 在 AndroidManifest.xml 中添加存储权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

iOS: 在 Info.plist 中添加图库权限描述:

<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问您的相册以保存图片。</string>
class ImageGallerySaver {
  // ... 其余成员省略(源码结构示意)
  static FutureOr<dynamic> saveImage(Uint8List imageBytes,
      {int quality = 80,
      String? name,
      bool isReturnImagePathOfIOS = false}) async {
    final result =
        await _channel.invokeMethod('saveImageToGallery', <String, dynamic>{
      'imageBytes': imageBytes,
      'quality': quality,
      'name': name,
      'isReturnImagePathOfIOS': isReturnImagePathOfIOS
    });
    return result;
  }
  // ... 其余成员省略
}

/// 保存图片
  Future<dynamic> saveImage(Uint8List imageBytes) async {
    final result = await ImageGallerySaver.saveImage(imageBytes);
    return result;
  }

实战案例

这个场景我还没在项目里真正用过,下面是按 screenshot 插件文档和官方 RenderObject 文档提炼的关键点,动手前建议先看一遍。pixelRatio 是第一个 trade-off:toImage(pixelRatio: 3.0) 清晰度够发朋友圈,但大屏截图时内存直接翻几倍,低端机可能 OOM,我的建议是按需取设备 pixelRatio,别无脑拉满。第二个坑是 PlatformView:地图、WebView 这类原生视图走的不是 Flutter 的绘制管线,RepaintBoundary 截出来是黑块,这是架构限制不是 bug,真要截只能走原生截图再拼。第三,保存到相册前记得先申请权限,Android 13+ 用 photos 权限,别再要 WRITE_EXTERNAL_STORAGE。

注:正文里的 image_gallery_saver 已停更(不支持 AGP 8+),新项目请用 image_gallery_saver_plus,类名改为 ImageGallerySaverPlus,用法基本一致。

参考:screenshot 插件文档、Flutter 官方文档 RenderRepaintBoundary 章节


分享这篇文章:


相关文章

上一篇
Flutter GridView 组件详解
下一篇
Flutter PageView 组件详解