水印组件 WaterMark 的定义:
class WaterMark extends StatefulWidget {
WaterMark({
Key? key,
this.repeat = ImageRepeat.repeat,
required this.painter,
}) : super(key: key);
/// 单元水印画笔
final WaterMarkPainter painter;
/// 单元水印的重复方式
final ImageRepeat repeat;
@override
State<WaterMark> createState() => _WaterMarkState();
}
State 实现:
class _WaterMarkState extends State<WaterMark> {
late Future<MemoryImage> _memoryImageFuture;
@override
void initState() {
// 缓存的是promise
_memoryImageFuture = _getWaterMarkImage();
super.initState();
}
@override
Widget build(BuildContext context) {
return SizedBox.expand( // 水印尽可能大
child: FutureBuilder(
future: _memoryImageFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
// 如果单元水印还没有绘制好先返回一个空的Container
return Container();
} else {
// 如果单元水印已经绘制好,则渲染水印
return DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: snapshot.data, // 背景图,即我们绘制的单元水印图片
repeat: widget.repeat, // 指定重复方式
alignment: Alignment.topLeft,
),
),
);
}
},
),
);
}
@override
void didUpdateWidget(WaterMark oldWidget) {
... //待实现
}
// 离屏绘制单元水印并将绘制结果转为图片缓存起来
Future<MemoryImage> _getWaterMarkImage() async {
... //待实现
}
@override
void dispose() {
...// 待实现
}
}
通过 DecoratedBox 来实现背景图重复,同时我们在组件初始化时开始进行离屏绘制单元水印,并将结果缓存在 MemoryImage 中,因为离屏绘制是一个异步任务,所以直接缓存 Future 即可
需要注意,当组件重新build时,如果画笔配置发生变化,则需要重新绘制单元水印并缓存新的绘制结果:
@override
void didUpdateWidget(WaterMark oldWidget) {
// 如果画笔发生了变化(类型或者配置)则重新绘制水印
if (widget.painter.runtimeType != oldWidget.painter.runtimeType ||
widget.painter.shouldRepaint(oldWidget.painter)) {
//先释放之前的缓存
_memoryImageFuture.then((value) => value.evict());
//重新绘制并缓存
_memoryImageFuture = _getWaterMarkImage();
}
super.didUpdateWidget(oldWidget);
}
注意,在重新绘制单元水印之前要先将旧单元水印的缓存清理掉,清理缓存可以通过调用 MemoryImage 的 evict 方法。同时,当组件卸载时,也要释放缓存:
@override
void dispose() {
//释放图片缓存
_memoryImageFuture.then((value) => value.evict());
super.dispose();
}
离屏绘制:
接下来就需要重新绘制单元水印了,调用 _getWaterMarkImage()
方法即可,该方法的功能是离屏绘制单元水印并将绘制结果转为图片缓存起来
// 离屏绘制单元水印并将绘制结果保存为图片缓存起来
Future<MemoryImage> _getWaterMarkImage() async {
// 创建一个 Canvas 进行离屏绘制,细节和原理请查看本书后面14.5节。
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
// 绘制单元水印并获取其大小
final size = widget.painter.paintUnit(
canvas,
MediaQueryData.fromWindow(ui.window).devicePixelRatio,
);
final picture = recorder.endRecording();
//将单元水印导为图片并缓存起来
final img = await picture.toImage(size.width.ceil(), size.height.ceil());
final byteData = await img.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
return MemoryImage(pngBytes);
}