本文已参与「新人创作礼」活动,一起开启创作之路。

这是【Flutter 问题系列第 22 篇】,如果觉得有用的话,欢迎关注专栏。

关于在 Flutter 中如何截取屏幕,以及如何将截图保存到相册的文章少之又少,即使有,也是错误一大片,有的甚至运行后都报错,就这都直接发出来了,真是可恶啊!

所以我整理了这篇博客,实现了两个功能

  • 如何截取整个屏幕或屏幕中的某一部分,并显示到页面中
  • 如何将截取的图片,保存到相册中

这两个功能都已亲测并无问题,源码会全部奉上,如果有用,希望可以给个三连,接下来是博客正文。

一:如何截取屏幕,并显示到页面中

依照国际惯例,先上效果图(没有先看到效果图,估计你们都会走吧~)

【Flutter 问题系列第 22 篇】在 Flutter 中如何截取屏幕并显示到页面中,以及如何将截图保存到相册

由效果图可以看出来,截取到的每一帧都是不同的图片,功能实现了,接下来就是如何实现功能的代码了。

1:RepaintBoundary 组件介绍

我们知道在 Flutter 中万物皆组件,所以接下来要说的截图其实也是一个组件,与其说是截取屏幕,不如说是截取组件。

而这个组件的名称就是 RepaintBoundary ,源码如下所示

 RepaintBoundary({ Key key, Widget child })

使用起来也很简单,直接套在你想要截的组件上面就行了,如果你要截取的是整个页面,套在 Scaffold 外面即可。

因为 RepaintBoundary 继承自 SingleChildRenderObjectWidget 而我们又需要获取到被截取组件的状态,所以第一个参数 Key 的类型应为 GlobalKey,如下所示

GlobalKey _repaintKey = GlobalKey(); // 可以获取到被截图组件状态的 GlobalKey

而第二个参数就是你需要截取的组件,如下代码所示

RepaintBoundary(
  key: _repaintKey,
  child: Image.asset("assets/girl.gif", width: 200, height: 200, fit: BoxFit.cover),
)

2:如何截图

接下来要说的是最核心的部分了,就是如何获取到截取图片的数据。

这里我直接把代码复制到下方了,关键代码都有解释,相信大家一看就懂了。

 /// 获取截取图片的数据
  Future<Uint8List> _getImageData() async {
    BuildContext buildContext = _repaintKey.currentContext;
    if (buildContext != null) {
      RenderRepaintBoundary boundary = buildContext.findRenderObject();
      // 第一次执行时,boundary.debugNeedsPaint 为 true,此时无法截图(如果为true时直接截图会报错)
      if (boundary.debugNeedsPaint) {
        // 延时一定时间后,boundary.debugNeedsPaint 会变为 false,然后可以正常执行截图的功能
        await Future.delayed(Duration(milliseconds: 20));
        // 重新调用方法
        return _getImageData();
      }
      // 获取当前设备的像素
      double dpr = ui.window.devicePixelRatio;
      // pixelRatio 代表截屏之后的模糊程度,因为不同设备的像素比不同
      // 定义一个固定数值显然不是最佳方案,所以以当前设备的像素为目标值
      ui.Image image = await boundary.toImage(pixelRatio: dpr);
      ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
      Uint8List imageBytes = byteData.buffer.asUint8List();
      // 返回图片的数据
      return imageBytes;
    }
  }

这里将图片数据以 Uint8List 的格式返回,方便后面显示图片。

3:如何在页面中显示截图

我们可以通过 Image.memory(); 方法从内存中加载图片,而该方法需要传入图片的数据,数据类型是 Uint8List ,这也是为什么要把图片数据以 Uint8List 类型返回了。

如果要显示的截图有多张,则定义一个列表

List<Uint8List> _images = []; // 存放所有截图的列表

然后当点击底部按钮时,执行如下函数

/// 执行截图并显示到页面中
void _doScreenShots() async {
  Uint8List data = await _getImageData();
  _images.add(data);
  setState(() {});
}

最后就是遍历这个列表,把图片显示出来就行了,如下代码所示

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
  ),
  itemCount: _images.length,
  itemBuilder: (BuildContext context, int index) {
    if (_images.isEmpty) {
      return Container();
    }
    return Image.memory(_images[index], fit: BoxFit.cover);
  },
)

至此,如何截取屏幕,并显示到页面中便介绍完毕了,一定要注意的是,当 _getImageData() 方法中的 boundary.debugNeedsPaint 为 true 时,一定不要去截图,一定不要去截图,一定不要去截图,否则会报

‘!debugNeedsPaint’:is not true 的错误,切记!!!

二:如何将截取到的图片,保存到相册中

如何截图已经说过了,如何将截图保存到相册中呢?

1:配置权限、引用插件

保存到相册的话就要涉及到存储文件的权限,以及如何把图片保存到相册的问题了。

这里引用两个插件

  • 权限控制插件 permission_handler
  • 图片存储到相册插件 image_gallery_saver

然后在 pubspec.yaml 文件中引入这两个插件,如下所示

dependencies:
  permission_handler: ^8.1.1 # 权限控制插件 by Allen Su
  image_gallery_saver: ^1.6.9 # 图片存储到相册插件 by Allen Su

安卓系统,需要在 android/app/src/main/AndroidManifest.xml 文件中添加如下代码

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!--写入外部存储权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!--读取外部存储权限-->

苹果系统,需要在 ios/Runner/Info.plist 文件中添加如下代码

<key>NSPhotoLibraryAddUsageDescription</key>
<string>请允许APP保存图片到相册</string>

(因博主是从事安卓开发的,关于 ios 这里是翻阅的资料,并没有证实,应该没有什么问题)

2:存储图片到相册

权限问题解决了,接下来就是如何把截图存储到本地相册了,很简单,其实一行代码就可以了,下面的代码包含获取存储权限和存储图片到相册,如下所示

/// 执行存储图片到本地相册
void _doSaveImage() async {
  // 如果用户已授权存储权限
  if (await Permission.storage.request().isGranted) {
    Uint8List data = await _getImageData();
    await ImageGallerySaver.saveImage(data);
  } else {
    // 没有存储权限时,弹出没有存储权限的弹窗
  }
}

当点击按钮时,是获取权限,如下图所示

【Flutter 问题系列第 22 篇】在 Flutter 中如何截取屏幕并显示到页面中,以及如何将截图保存到相册

点击允许后,图片会自动保存到相册,如下图所示

【Flutter 问题系列第 22 篇】在 Flutter 中如何截取屏幕并显示到页面中,以及如何将截图保存到相册

可以看到,这是已经保存到相册后的视图了,至此,关于在 Flutter 中如何截取屏幕并显示到页面中,以及如何将截图保存到相册便介绍完毕了,按照我写的一步一步来,应该不会有什么问题。

你的问题得到解决了吗?欢迎在评论区留言。

赠人玫瑰,手有余香,如果觉得文章不错,希望可以给个一键三连,感谢。


结束语

Google 的 Flutter 越来越火,截止 2021年6月3日 GitHub 标星已达 123K,Flutter 毅然是一种趋势,所以作为前端开发者,没有理由不趁早去学习。

无论你是 Flutter 新手还是已经入门了,不妨先点个关注,后续我会将 Flutter 中的常用组件(含有源码分析、组件的用法及注意事项)以及可能遇到的问题写到稀土博客中,希望自己学习的同时,也可以帮助更多的人。