在上一篇文章中,我们谈了 Flutter 中下载并保存图片为文件 的内容,今天,我们来说说怎么将 widget
生成截图,并且怎么通过接口上传。
生成截图
我们指定生成图片的 widget
区域:
final GlobalKey boundaryKey = GlobalKey();
// ...
child: RepaintBoundary(
key: boundaryKey,
child: const Text('测试内容'), // 这里可以替换成自定义的 widget 内容
)
上面,我们通过 RepaintBoundary
包裹了需要生成图片的 widget
。下面,我们触发方法,生成图片:
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.insert_photo_rounded),
onPressed: () async {
try {
// widget 转换成图片
RenderRepaintBoundary? boundary = boundaryKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
ui.Image? image = await boundary?.toImage()
final byteData = await image?.toByteData(format: ui.ImageByteFormat.png);
// 弹窗展示
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
'展示截图',
style: TextStyle(
fontSize: 16.0,
),
),
content: SizeBox(
width: 520.0,
height: 160.0
child: Image.memory(
byteData!.buffer.asUnit8List(),
fit: BoxFit.fill,
),
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'取消',
style: TextStyle(
color: Colors.grey,
),
),
),
],
);
}
);
} catch(e) {
debugPrint(e.toString());
}
}
)
}
上面代码中,我们指定了 boundaryKey
区域,通过 boundary?.toImage()
生成了图片。然后,为了方便我们查看生成的图片,我们写了个弹窗,通过 Image.memory
将保存在内存的图片数据流展示出来。
保存为临时文件
接着,我们将该图片数据流写成临时的文件。
我们需要用到包 path_provider
,截止发稿,该版本为 2.1.3
。
该插件方便我们查找文件系统的常用位置。
代码语言:javascript复制onPressed: () async {
// widget 转换成图片
RenderRepaintBoundary? boundary = boundaryKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
ui.Image? image = await boundary?.toImage()
final byteData = await image?.toByteData(format: ui.ImageByteFormat.png);
// 创建一个临时的文件进行上传
final output = await getTemporaryDirectory();
final file = File("${output.path}/${(new DateTime.now()).millisecondsSinceEpoch}.png");
await file.writeAsBytes(byteData!.buffer.asUint8List());
}
getTemporaryDirectory()
用于获取临时文件目录的路径。不同操作系统,获取的临时文件目录路径可能会有所不同,这个我们不需要关心。然后,我们生成了当前时间戳的文件名 DateTime.now()).millisecondsSinceEpoch}.png
,并把生成的截图数据写入文件中。
接口上传
为了方便理解,我们扩展演示,随便增加一个字段,上传的 Form
表单内容如下:
final TextEditingController _descriptionController = TextEditingController(text: '描述内容');
// 其他代码
Form(
child: Column(
children: <Widget>[
Container(
width: 520.0,
height: 160.0,
child: Image.memory(
byteData!.buffer.asUnit8List(),
fit: BoxFit.fill,
),
),
TextFormField(
controller: _descriptionController,
decoration: InputDecoration(
labelText: '描述'
),
),
],
),
),
我们增加了 description
图片描述的字段,所以,我们将上传如下的字段到服务器:
{
"file": *,
"description": *,
}
通过 Bloc
管理:
onPressed: () {
fileBloc.add(
FileSaveEvent(
file: file,
description: '相关描述',
),
);
}
代码语言:javascript复制// file_event.dart
abstract class FileEvent {}
class FileSaveEvent extends FileEvent {
final File file;
final String description;
FileSaveEvent({
required this.file,
required this.description,
});
}
代码语言:javascript复制PS 关于 bloc 的知识点,我们将会开一篇文章进行讲解,这里先一笔带过
// file_bloc.dart
FileBloc() : super(FileState().init()) {
on<FileSaveEvent>(_onFileSaveEvent);
}
Future<String> _onFileSaveEvent(FileSaveEvent event, Emitter<FileState> emit) async {
Map<String, dynamic> snapshot = {
'description': event.description ?? '',
'type': 'IMAGE',
};
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile(event.file.path),
'snapshot': jsonEncode(snapshot)
});
try {
// 接口调用
await apiService?.postFileEvent(recordId, formData);
} catch(e) {
debugPrint(e.toString());
return '保存失败';
}
return '保存成功';
}
PS 上面代码上的
FileState
用于管理数据的状态,比如请求成功,失败,获取数据列表的管理等。
我们假设存在一个 POST
保存文件的接口服务 apiService?.postFileEvent(id, formData)
,其中 recordId
是记录的 id
,formData
是放在 body
请求的数据。我们把 description
当作 snapshot
变量中的一个属性进行扩展。
我们可以在 FileState
文件中,定义接口执行的状态,并在页面中监听该状态的更改来展示接口操作成功与否的 Toast
。或者,我们也可以通过 EventBus
,在 await apiService?.postFileEvent(recordId, formData);
下进行 eventBus.fire
发射状态,在页面中进行监听 eventBus.on().listen
。这个根据实际情况来使用。
感谢读者捧场,谢谢