一、屏幕截图
利用RepaintBoundary
组件截屏,RepaintBoundary
能够截图是因为它创建了一个独立的绘制层,Flutter 可以捕获该层的内容并转换为图像。使用时,将屏幕上需要截取的内容用RepaintBoundary
组件包裹,并传入全局变量GlobalKey
。
dart
GlobalKey pictureKey = GlobalKey();
RepaintBoundary(
key: pictureKey,
child: Container(child:...)
)
实现截图的步骤
- 通过
GlobalKey
获取RepaintBoundary
的引用。 - 调用
RepaintBoundary
的toImage
方法,Flutter 会将该边界内的内容渲染为ui.Image
对象。 - 将
ui.Image
转换为常见的图像格式。 - 可以借助
path_provider
插件获取app目录并存储图片。
dart
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'package:flutter/rendering.dart';
import 'package:path_provider/path_provider.dart';
Future<void> _captureImage() async {
RenderObject? boundary = pictureKey.currentContext?.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
Directory directory = await getTemporaryDirectory();
String imagePath =
'${directory.path}/${DateTime.now().microsecondsSinceEpoch}.png';
await File(imagePath).writeAsBytes(pngBytes);
}
- 如果是
webview
,使用RepaintBoundary
是无法截图的,可以使用webview
自带的截图方法,如FLutter中经常使用的flutter_inappwebview
插件,可以使用webController
的takeScreenshot
方法获取截图。
dart
Uint8List? uint8List = await webController?.takeScreenshot();
二、图片加水印
上面已经通过RepaintBoundary
拿到了ui.Image
,可以利用ui.Image
来创建Canvas
画布,利用画布添加水印,可以添加图片、文字,最后,通过ui.Picture
的toImage
方法获取带水印的图片。
dart
Future<void> _captureImage() async {
RenderObject? boundary = pictureKey.currentContext?.findRenderObject();
ui.Image image = await boundary.toImage();
// 创建一个 Canvas 来绘制水印
ui.PictureRecorder recorder = ui.PictureRecorder();
Canvas canvas = Canvas(recorder);
// 将原始图像绘制在canvas上
canvas.drawImage(image, Offset.zero, Paint());
// 添加图片水印
// 从assets中读取水印图片
ByteData waterData =
await rootBundle.load('assets/images/water_mark.png');
ui.Image waterImage =
await decodeImageFromList(waterData.buffer.asUint8List());
int width = image.width;
int height = image.height;
canvas.drawImageNine(
waterImage,
// 要绘制的水印图片区域
ui.Rect.fromLTWH(
0, 0, waterImage.width.toDouble(), waterImage.height.toDouble()),
// 绘制水印图片的目标区域,如:以原始图片中心点为中心,绘制宽100高100的水印图片
ui.Rect.fromCenter(
center: Offset(width / 2, height / 2),
width: 100,
height: 100),
Paint());
ui.Picture picture = recorder.endRecording();
// 获取带水印的图片
ui.Image newImg = await picture.toImage(width, height);
...
}
如果是整张水印铺满原始图片的情况,直接指定水印图片宽高容易造成水印图片的拉伸变形,所以最好是保持水印图片的宽高比。可以通过计算原始图片的宽高比和水印图片的宽高比来进行判断:
如下图,把整张水印图片铺在原始图片上,为了避免水印图片拉伸变形,需要保持水印图片的宽高比。
- 如果原始图片更宽,以原始图片的高为水印图片高,按水印图片宽高比计算出对应的水印图片宽度
- 如果水印图片更宽,以原始图片的宽度为水印图片宽,按水印图片宽高比计算出对应的水印图片高度
dart
// 原始图片宽高比
double imgRatio = width / height;
// 水印图片宽高比
double waterImgRatio = waterImage.width / waterImage.height;
double dstWidth = width.toDouble();
double dstHeight = height.toDouble();
if (imgRatio > waterImgRatio) {
// 如果原始图片更宽,以原始图片的高为水印图片高,按水印图片宽高比计算出对应的水印图片宽度
dstWidth = waterImgRatio * dstHeight;
} else {
// 反之,水印图片更宽,以原始图片的宽度为水印图片宽,按水印图片宽高比计算出对应的水印图片高度
dstHeight = dstWidth / waterImgRatio;
}
canvas.drawImageNine(
waterImage,
ui.Rect.fromLTWH(
0, 0, waterImage.width.toDouble(), waterImage.height.toDouble()),
ui.Rect.fromCenter(
center: Offset(width / 2, height / 2),
width: dstWidth,
height: dstHeight),
Paint());
除了添加图片,还可以添加文字水印:
dart
final textPainter = TextPainter(
text: TextSpan(
text: '水印',
style: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 40,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
// 在原始图片中心绘制水印文字
textPainter.paint(canvas, Offset(
(width - textPainter.width) / 2,
(height - textPainter.height) / 2)
);
三、保存图片到相册
上文已经获取到了带水印的图片,一般用于保存或分享,可以借助image_gallery_saver
插件来保存图片到相册。
dart
ByteData? byteData = await newImg.toByteData(format: ui.ImageByteFormat.png);
Uint8List? pngUint8List = byteData?.buffer.asUint8List();
saveToAlbum(pngUint8List);
Future<String> saveToAlbum(
Uint8List imageBytes, {
int quality = 100,
String? name,
}) async {
dynamic result = await ImageGallerySaver.saveImage(
imageBytes,
quality: quality,
name: name,
isReturnImagePathOfIOS: true,
);
if (result is! Map) {
return Future.value('');
}
return Future.value(result.['filePath']??''));
}