本文已参与「新人创作礼」活动,一起开启创作之路。
这是【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 中如何截取屏幕并显示到页面中,以及如何将截图保存到相册便介绍完毕了,按照我写的一步一步来,应该不会有什么问题。
你的问题得到解决了吗?欢迎在评论区留言。
赠人玫瑰,手有余香,如果觉得文章不错,希望可以给个一键三连,感谢。
结束语
Google 的 Flutter 越来越火,截止 2021年6月3日 GitHub 标星已达 123K,Flutter 毅然是一种趋势,所以作为前端开发者,没有理由不趁早去学习。
无论你是 Flutter 新手还是已经入门了,不妨先点个关注,后续我会将 Flutter 中的常用组件(含有源码分析、组件的用法及注意事项)以及可能遇到的问题写到稀土博客中,希望自己学习的同时,也可以帮助更多的人。