文本处理

Catalogue   

TextPainter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
/// 绘制文本
const textStyle = TextStyle(color: Colors.black, fontSize: 24);
const textSpan = TextSpan(text: 'Hello, World', style: textStyle);
final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr);
textPainter.layout(minWidth: 0, maxWidth: size.width);
const offset = Offset(50, 100);
textPainter.paint(canvas, offset);
}

@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}

TextPainter封装了Paragraph,用于绘制文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class TextPainter {
TextPainter({
InlineSpan? text,//文本内容
TextAlign textAlign = TextAlign.start,//对齐方式
TextDirection? textDirection,
double textScaleFactor = 1.0,
int? maxLines,
String? ellipsis,
Locale? locale,
StrutStyle? strutStyle,
TextWidthBasis textWidthBasis = TextWidthBasis.parent,
ui.TextHeightBehavior? textHeightBehavior,
})


void layout({ double minWidth = 0.0, double maxWidth = double.infinity }) {
if (_paragraph != null && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth) {
return;
}

if (_rebuildParagraphForPaint || _paragraph == null) {
_createParagraph();
}
_lastMinWidth = minWidth;
_lastMaxWidth = maxWidth;
// A change in layout invalidates the cached caret and line metrics as well.
_lineMetricsCache = null;
_previousCaretPosition = null;
_layoutParagraph(minWidth, maxWidth);
_inlinePlaceholderBoxes = _paragraph!.getBoxesForPlaceholders();
}

void _layoutParagraph(double minWidth, double maxWidth) {
_paragraph!.layout(ui.ParagraphConstraints(width: maxWidth));//布局
if (minWidth != maxWidth) {
double newWidth;
switch (textWidthBasis) {
case TextWidthBasis.longestLine:
newWidth = _applyFloatingPointHack(_paragraph!.longestLine);
case TextWidthBasis.parent:
newWidth = maxIntrinsicWidth;
}
newWidth = clampDouble(newWidth, minWidth, maxWidth);
if (newWidth != _applyFloatingPointHack(_paragraph!.width)) {
_paragraph!.layout(ui.ParagraphConstraints(width: newWidth));
}
}
}

void paint(Canvas canvas, Offset offset) {
final double? minWidth = _lastMinWidth;
final double? maxWidth = _lastMaxWidth;
if (_paragraph == null || minWidth == null || maxWidth == null) {
throw StateError(
'TextPainter.paint called when text geometry was not yet calculated.\n'
'Please call layout() before paint() to position the text before painting it.',
);
}

if (_rebuildParagraphForPaint) {
_createParagraph();
_layoutParagraph(minWidth, maxWidth);
}
canvas.drawParagraph(_paragraph!, offset);//绘制
}

ui.Paragraph? _paragraph; //封装的Paragraph

ui.Paragraph _createParagraph() {
final InlineSpan? text = this.text;
if (text == null) {
throw StateError('TextPainter.text must be set to a non-null value before using the TextPainter.');
}
final ui.ParagraphBuilder builder = ui.ParagraphBuilder(_createParagraphStyle());
text.build(builder, textScaleFactor: textScaleFactor, dimensions: _placeholderDimensions);
_inlinePlaceholderScales = builder.placeholderScales;
final ui.Paragraph paragraph = _paragraph = builder.build();
_rebuildParagraphForPaint = false;
return paragraph;
}


}

Paragraph

TextPainter通过封装Paragraph来绘制文本。绘制的基本流程:

  1. 通过_createParagraphStyle创建ParagraphStyle
  2. 根据样式创建ParagraphBuilder
  3. 通过ParagraphBuilder处理文本内容和样式
  4. Paragraph的layout进行布局
  5. Canvas的drawParagrahp进行绘制

代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

// 第一步
final paragraphStyle = ParagraphStyle(
// 字体方向,有些国家语言是从右往左排版的
textDirection: TextDirection.ltr,
// 字体对齐方式
textAlign: TextAlign.justify,
fontSize: 14,
maxLines: 2,
// 字体超出大小时显示的提示
ellipsis: '...',
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
height: 5,
// 当我们设置[TextStyle.height]时 这个高度是否应用到字体顶部和底部
textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: true, applyHeightToLastDescent: true));
// 第二步 与第三步
final paragraphBuilder = ParagraphBuilder(paragraphStyle)
..addText('ParagraphBuilder类接收一个参数,是一个ParagraphStyle类,用于设置字体基本样式,例如字体方向、对齐方向、字体粗细等,下面我们分几个步骤来绘制文字');
// 第四步
var paragraph = paragraphBuilder.build();
// 第五步
paragraph.layout(ParagraphConstraints(width: 300));
// 画一个辅助矩形(可以通过paragraph.width和paragraph.height来获取绘制文字的宽高)
canvas.drawRect(Rect.fromLTRB(50, 50, 50 + paragraph.width, 50 + paragraph.height),
Paint()..color = Colors.red.withOpacity(0.5));
// 第六步
canvas.drawParagraph(paragraph, Offset(50, 50));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class ParagraphBuilder {
/// 添加文本
void addText(String text) {
final String? error = _addText(text);
if (error != null) {
throw ArgumentError(error);
}
}
external String? _addText(String text);

/// 设置样式
void pushStyle(TextStyle style) {}


Paragraph build() {}

void addPlaceholder(double width, double height, PlaceholderAlignment alignment, {
double scale = 1.0,
double? baselineOffset,
TextBaseline? baseline,
}) {}
}

class Paragraph {

/// 根据约束布局
void layout(ParagraphConstraints constraints) {
_layout(constraints.width);
assert(() {
_needsLayout = false;
return true;
}());
}
@Native<Void Function(Pointer<Void>, Double)>(symbol: 'Paragraph::layout', isLeaf: true)
external void _layout(double width);

/// drawParagraph会调用该方法进行绘制
external void _paint(Canvas canvas, double x, double y);
}

参考