一. Paragraph
void drawParagraph(Paragraph paragraph, Offset offset)
Paragraph是Flutter中用于文字绘制的类,所有的文字最后都是通过它来绘制的。
Paragraph是一个没有构造函数的类,它只是提供一个宿主,用于最后的渲染。我们真正需要处理的是ParagraphBuilder这个类。
/// 1.生成 ParagraphStyle,可设置文本的基本信息
final paragraphStyle = ui.ParagraphStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
textDirection: TextDirection.ltr,
);
/// 2.根据 ParagraphStyle 生成 ParagraphBuilder
final paragraphBuilder = ui.ParagraphBuilder(paragraphStyle);
/// 3.添加样式和文字
paragraphBuilder
..pushStyle(ui.TextStyle(color: Colors.black, fontSize: 16))
..addText('黑色文字')
..pushStyle(ui.TextStyle(color: Colours.red, fontSize: 14))
..addText('红色文字');
/// 4.通过 build 取到 Paragraph
final paragraph = paragraphBuilder.build();
/// 5.根据宽高进行布局layout
paragraph.layout(ui.ParagraphConstraints(width: 200));
/// 6.绘制
canvas.drawParagraph(paragraph, const Offset(50, 50));

二. TextPainter
TextPainter是通过Paragraph封装而成,相比Paragraph它提供了更加强大的功能。
- 通过传入
TextSpan,实现多种不同效果的字体来支持富文本。 - 不像
Paragraph必须设置一个宽度,它可以不用初始化宽度,可以通过TextPainter().width来获取实际渲染宽度。
final textPainter = TextPainter()
..text = const TextSpan(text: '可多种不同效果的字体来支持富文本', style: TextStyle(color: Colors.white, fontSize: 20))
..textDirection = TextDirection.ltr
/// 可以传入minWidth、maxWidth来限制宽度,若不传文字会绘制在一行
..layout(maxWidth: 100);
/// 绘制矩形框,在文字绘制前可通过textPainter.width和textPainter.height来获取文字绘制的尺寸
canvas.drawRect(
Rect.fromLTWH(50, 50, textPainter.width, textPainter.height),
Paint()..color = Colors.blue,
);
/// 绘制文字
textPainter.paint(canvas, const Offset(50, 50));

三.drawImage
void drawImage(Image image, Offset offset, Paint paint)
这里的Image是dart:ui库中的Image,它保存了图片的一些基本信息并直接与引擎交互。
加载本地图片方法一:
Future<ui.Image> loadAssetImage(String asset) async {
final imageData = await rootBundle.load(asset);
final imageCodec = await ui.instantiateImageCodec(imageData.buffer.asUint8List());
final imageInfo = await imageCodec.getNextFrame();
return imageInfo.image;
}
加载本地图片方法二:
Future<ui.Image> _loadAssetImageByProvider(ImageProvider provider, {ImageConfiguration config = ImageConfiguration.empty}) async {
final completer = Completer<ui.Image>();
final stream = provider.resolve(config);
late ImageStreamListener listener;
listener = ImageStreamListener((frame, sync) {
final image = frame.image;
completer.complete(image);
stream.removeListener(listener);
});
stream.addListener(listener);
return completer.future;
}
/// 使用
_ loadAssetImageByProvider(AssetImage('assets/logo.png'))
/// 如果你的资源图片使用了分辨率加载(1.0x/2.0x/3.0x),不要使用rootBundle来加载图片,
/// rootBundle不会根据不同的上下文环境(比如不同的屏幕分辨率)加载不同版本的资源。
/// createLocalImageConfiguration中的DefaultAssetBundle类会结合当前的BuildContext来实例化与当前上下文环境相关联的AssetBundle对象。
_loadAssetImageByProvider(
'assets/logo.png',
config: createLocalImageConfiguration(context),
)
由于图片加载是异步过程,不能放在CustomPaint的paint方法中来加载,这里需要在外部使用一个StatefulWidget,加载完成后将获取到的Image传入CustomPaint中。此时再使用canvas.drawImage来绘制。
四. drawImageRect
void drawImageRect(Image image, Rect src, Rect dst, Paint paint)
-
src: 截取图片的一块区域,起始点相对于图片左上角。 -
dst:在canvas上的一块区域来绘制截取的图片,图片可能会被拉升。
可以用于实现图片缩放绘制:
if (logoImage != null) {
/// 原图绘制
canvas.drawImage(logoImage!, const Offset(50, 50), imagePaint);
/// 缩小图片尺寸绘制
canvas.drawImageRect(
logoImage!,
Rect.fromLTWH(0, 0, logoImage!.width.toDouble(), logoImage!.height.toDouble()),
Rect.fromLTWH(50, 150, 74, 16),
imagePaint,
);
}

五.drawImageNine
drawImageNine(Image image, Rect center, Rect dst, Paint paint)
-
center: 可拉伸或压缩的区域。 -
dst:在画布上绘制的目标矩形区域。
通过绘制两条水平线和两条垂直线将图像分割成9个部分,角落的4个区域会在不缩放的情况下被绘制在目标矩形,剩下的5个区域会通过拉伸或压缩来绘制,以便能够完全覆盖目标矩形。
六.drawPicture
通过传入一个Picture实例来进行绘制,而Picture需通过PictureRecorder来构造:
ui.Picture _getPictureFromCanvas() {
// 图片记录仪,记录一系列图片操作
final pictureRecorder = ui.PictureRecorder();
/// 创建一个Canvas来记录
final canvas = Canvas(pictureRecorder);
/// 开始绘制
canvas.drawCircle(
const Offset(100, 100), 100,
Paint()..color = Colors.red,
);
/// 结束记录,生成Picture
final picture = pictureRecorder.endRecording();
return picture;
}
drawPicture方法也是需要从外部传入一个Picture对象,不能在CustomPainer的paint方法中使用PictureRecorder来记录并绘制UI:
class MyCustomPainter extends CustomPainter {
final ui.Picture? picture;
MyCustomPainter(this.picture);
@override
void paint(Canvas canvas, Size size) {
if (picture != null) {
canvas.drawPicture(picture!);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true
}
