Flutter学习:使用CustomPaint绘制文字
Flutter学习:认识CustomPaint组件和Paint对象
Flutter学习:使用CustomPaint绘制路径
Flutter学习:使用CustomPaint绘制图形
Flutter学习:使用CustomPaint绘制文字
Flutter学习:使用CustomPaint绘制图片
drawParagraph
绘制文本。需要传递2个参数:
Paragraph paragraph
:文本对象Offset offset
:文本绘制的位置
ParagraphBuilder paragraphBuilder = ParagraphBuilder(ParagraphStyle())..addText('Hello World');
ParagraphConstraints paragraphConstraints = ParagraphConstraints(width: size.width);
Paragraph paragraph = paragraphBuilder.build() ..layout(paragraphConstraints);
canvas.drawParagraph(paragraph, const Offset(350, 180));
绘制的文字默认大小为14,颜色为白色。如果你直接使用以上代码并不能绘制成我这样。这里我用SizedBox.expand
包裹了CustomPaint
,否则你的size
值为0,ParagraphConstraints(width: size.width)
设置的也是0,什么都绘制不出来。当然,你也可以使用Center
包裹CustomPaint
,需要重新设置一下offset
,显示出来的效果如下:
canvas.drawParagraph(paragraph, const Offset(0, -100));
Offset没有什么好说的,我们着重来研究研究Paragraph对象。
从以上代码我们可以得知,绘制文字首先需要一个ParagraphBuilder
对象。该对象只有一个style
属性,该属性是一个ParagraphStyle
对象,看来我们得把矛头专项该对象了。
ParagraphBuilder
属性
通过名称我们可以得知,该对象是用来设置文本风格的,它有以下多个属性:
TextAlign? textAlign
:文本对齐方式TextDirection? textDirection
:文本方向int? maxLines
:文本最大行String? fontFamily
:文本字体风格double? fontSize
:字体大小double? height
:文本行高TextHeightBehavior? textHeightBehavior
:指定如何将行高应用于第一行的上升和最后一行的下降FontWeight? fontWeight
:字重(粗细)FontStyle? fontStyle
:绘制字母时使用的字体变体(例如,斜体)StrutStyle? strutStyle
:支柱的风格。 Strut 定义了一组最小垂直行高相关的指标,可用于获得更高级的行间距行为String? ellipsis
:用于省略溢出文本的字符串。如果maxLines
不为 null,则ellipsis
(如果有)将应用于最后渲染的行,如果该行超出宽度约束。如果maxLines
为 null,则将ellipsis
应用于超出宽度约束的第一行,并删除后续行。宽度约束是在传递给Paragraph.layout方法Locale? locale
:用于选择区域特定字形的语言环境
我们可以发现,大部分属性和TextStyle
是一样的,我就只讲几个不怎么常用的。
TextHeightBehavior
bool applyHeightToFirstAscent
:当为 true 时, TextStyle.height修饰符将应用于第一行的上升。当为 false 时,将使用字体的默认上升。默认为truebool applyHeightToLastDescent
:当为 true 时, TextStyle.height修饰符将应用于最后一行的下降。当为 false 时,将使用字体的默认下降。默认为trueTextLeadingDistribution leadingDistribution
:前导如何分布在文本之上和之下。默认为TextLeadingDistribution.proportional
ParagraphBuilder paragraphBuilder = ParagraphBuilder(
ParagraphStyle(fontSize: 24, height: 1.5, textHeightBehavior: const TextHeightBehavior()),
)..addText('Hello World' * 10);
ParagraphConstraints paragraphConstraints = ParagraphConstraints(width: size.width / 3 * 2);
Paragraph paragraph = paragraphBuilder.build()..layout(paragraphConstraints);
canvas.drawParagraph(paragraph, Offset(size.width / 3 / 2, 100));
测试了一下,只有applyHeightToFirstAscent
这个值对文字的排版有影响,其他两个属性没有效果。可能和字体有关,这个属性一般也用不到,默认就好。
StrutStyle
String? fontFamily
:字体List<String>? fontFamilyFallback
:字体列表。当在fontFamily中找不到字体时将搜索该列表double? fontSize
:字体大小double? height
:字体行高TextLeadingDistribution? leadingDistribution
:由height
乘数增加的额外垂直空间应该如何分布在文本之上和之下,独立于leading
(它总是均匀分布在文本之上和之下)。默认为段落的TextHeightBehavior的主要分布double? leading
:行与行之间的最小行距,是字体大小的倍数。必须提供fontSize
才能使该属性生效。无论 leadingDistribution是什么,此属性添加的前导均匀分布在文本上方和下方FontWeight? fontWeight
:字重(粗细)FontStyle? fontStyle
:绘制字母时使用的字体变体(例如,斜体)bool? forceStrutHeight
:当为真时,段落将强制所有行从基线到基线完全是(height +leading) * fontSize
高。 TextStyle不再能够影响行高,并且任何高字形都可能与上面的行重叠。如果指定了fontFamily
,则第一行的总上升将是fontFamily
的Ascent + half-leading
和(height +leading) * fontSize
的最小值。否则,将由第一个文本的Ascent + half-leading
决定。默认为 false
方法
addText
:要绘制的文字内容pushStyle
:将给定样式应用于添加的文本pop
:删除最近一次调用pushStyle的效果addPlaceholder
:向段落添加内联占位符空间build
:应用给定的段落样式并返回一个包含添加的文本和相关样式的Paragraph 。调用此函数后,段落构建器对象无效,无法进一步使用placeholderCount
:当前在段落中的占位符的数量placeholderScales
:段落中占位符的比例
addText、pushStyle和pop
ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
ui.ParagraphStyle(fontSize: 24),
);
paragraphBuilder.pushStyle(ui.TextStyle(
color: Colors.black,
fontSize: 32,
));
paragraphBuilder.pushStyle(ui.TextStyle(
color: Colors.red,
fontSize: 32,
));
paragraphBuilder.pop();
paragraphBuilder.addText('吹梦到西洲');
ui.ParagraphConstraints paragraphConstraints = ui.ParagraphConstraints(width: size.width / 3 * 2);
ui.Paragraph paragraph = paragraphBuilder.build()..layout(paragraphConstraints);
canvas.drawParagraph(paragraph, Offset(size.width / 3 / 2, 100));
注意:pushStyle
中的设置会覆盖ParagraphStyle
,addText
必须放在后面。
addPlaceholder
paragraphBuilder.addPlaceholder(100, 80, PlaceholderAlignment.top);
PlaceholderAlignment还有 aboveBaseline、baseline、belowBaseline几个枚举值,这几个值的需要设置TextBaseline才能使用,但是我设置里TextBaseline还是没效果,你们可以自己试一下。
build
调用该方法会返回一个最终会绘制的Paragraph对象。
Paragraph
属性
height
:此段落占的高度width
:此段落的宽度alphabeticBaseline
:从段落顶部到第一行字母基线的距离,以逻辑像素为单位didExceedMaxLines
:如果有更多垂直内容,但文本被截断,则为真,要么是因为我们达到了maxLines文本行,要么是因为maxLines为空, ellipsis不为空,并且其中一行超出了宽度限制ideographicBaseline
:从段落顶部到第一行的表意基线的距离,以逻辑像素为单位longestLine
:段落最长的一行的长度maxIntrinsicWidth
:返回最小的宽度,超过这个宽度,增加宽度不会减少高度minIntrinsicWidth
:该段落可以在不绘制其内容的情况下的最小宽度
ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle());
paragraphBuilder.pushStyle(ui.TextStyle(
color: Colors.red,
fontSize: 32,
));
paragraphBuilder.addText('海水梦悠悠,君愁我亦愁。南风知我意,吹梦到西洲。');
ui.ParagraphConstraints paragraphConstraints = ui.ParagraphConstraints(width: size.width / 3 * 2);
ui.Paragraph paragraph = paragraphBuilder.build();
paragraph.layout(paragraphConstraints);
canvas.drawParagraph(paragraph, Offset(size.width / 3 / 2, 100));
I/flutter ( 7724): height: 138.0
I/flutter ( 7724): width: 261.0
I/flutter ( 7724): alphabeticBaseline: 37.119998931884766
I/flutter ( 7724): didExceedMaxLines: false
I/flutter ( 7724): ideographicBaseline: 46.33599853515625
I/flutter ( 7724): longestLine: 256.0
I/flutter ( 7724): maxIntrinsicWidth: 768.0
I/flutter ( 7724): minIntrinsicWidth: 256.0
方法
layout
:计算段落中每个字形的大小和位置computeLineMetrics
:返回LineMetrics的完整列表,详细描述每条布局线的各种指标。这可能会返回大量数据,因此不建议重复调用它。相反,缓存结果getBoxesForPlaceholders
:返回包含段落中所有占位符的文本框列表。框的顺序与通过ParagraphBuilder.addPlaceholder传入的顺序相同。TextBox的坐标相对于段落的左上角,其中正 y 值表示向下getLineBoundary
:返回给定TextPosition处的行的TextRange 。换行符(如果有)作为范围的一部分返回。这可能会很昂贵,因为它需要计算线路指标,因此请谨慎使用getBoxesForRange
:返回包含给定文本范围的文本框列表。boxHeightStyle和boxWidthStyle参数允许自定义框的垂直和水平绑定方式。两个样式参数都默认为tight选项,这将提供紧密贴合的框并且不会考虑任何行间距。TextBox 的坐标相对于段落的左上角,其中正 y 值表示向下getPositionForOffset
:返回最接近给定偏移量的文本位置getWordBoundary
:返回给定TextPosition处单词的TextRange
不属于单词的字符,例如空格、符号和标点符号,两边都有分词符。在这种情况下,此方法将返回offset, offset+1 。在 Unicode 标准附件 #29 中更精确地定义了单词边界
computeLineMetrics
获取每一行的数据,返回一个行列表
List<ui.LineMetrics> lines = paragraph.computeLineMetrics();
既然最后获取的是一个LineMetrics数组,我们就来看看LineMetrics对象。它有以下多个属性:
hardBreak
:如果此行以显式换行符结束(例如 '\n')或者是段落的结尾,则为true。否则为falseascent
:以 baseline 为界线,上部分的高descent
:以 baseline 为界线,下部分的高unscaledAscent
:忽略TextStyle设置的height,上部分的高height
:从顶部边缘到底部边缘的线的总高度width
:从最左侧字形的左边缘到最右侧字形的右边缘的线宽left
:线条左边缘的 x 坐标baseline
:此行从段落顶部开始的基线 y 坐标lineNumber
:行在整个段落中的编号,第一行索引为零
getBoxesForPlaceholders
获取所有的占位符的信息
paragraphBuilder.addPlaceholder(100, 80, PlaceholderAlignment.top);
paragraphBuilder.addPlaceholder(20, 40, PlaceholderAlignment.top);
paragraph.layout(paragraphConstraints);
List<TextBox> textBoxes = paragraph.getBoxesForPlaceholders();
[
TextBox.fromLTRBD(0.0, 7.0, 100.0, 87.0, TextDirection.ltr),
TextBox.fromLTRBD(100.0, 7.0, 120.0, 47.0, TextDirection.ltr),
]
getLineBoundary
用来获取当前索引在绘制文字的第几行的范围
该方法需要传入一个TextPosition对象,TextPosition有两属性:
offset
:索引affinity
:关于 affinity 的介绍可以查看这里
TextRange textRange = paragraph.getLineBoundary(const TextPosition(offset: 10));
TextRange(start: 8, end: 16)
getBoxesForRange
获取范围内容所组成的矩形
List<TextBox> textBox = paragraph.getBoxesForRange(4, 12);
[
TextBox.fromLTRBD(128.0, -0.3, 256.0, 46.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 45.7, 128.0, 92.0, TextDirection.ltr)
]
getPositionForOffset
返回离坐标最近的字符串的索引
TextPosition textPosition = paragraph.getPositionForOffset(const Offset(100, 20));
TextPosition(offset: 3, affinity: TextAffinity.downstream)
getWordBoundary
如果存在绘制的文本中,就返回位置,不存在就返回-1
TextRange textRange = paragraph.getWordBoundary(const TextPosition(offset: 12));
TextRange(start: 12, end: 13)
layout
需要传递一个ParagraphConstraints对象,用来限制文字的显示宽度
ParagraphConstraints paragraphConstraints = ParagraphConstraints(width: size.width / 3 * 2);
TextPainter
创建一个绘制给定文本的文本画家。
属性
InlineSpan? text
:设置显示的元素。虽然是可选,但是在调用layout前不能为nullTextAlign textAlign
:文本对齐方式。默认为TextAlign.startTextDirection? textDirection
:文字方向。虽然是可选,但是在调用layout前不能为nulldouble textScaleFactor
:文字放大倍数。默认为1.0int? maxLines
:最多是多少行String? ellipsis
:使用省略号表示文本已溢出Locale? locale
:用于选择区域特定字形的语言环境StrutStyle? strutStyle
:具体用法查看drawParagraph的用法介绍TextWidthBasis textWidthBasis
:测量一行或多行文字宽度的不同方法TextWidthBasis.longestLine
:宽度将恰好足以包含最长的线并且不再。一个常见的用例是聊天气泡TextWidthBasis.parent
:多行文本将占据父级给出的全宽。对于单行文本,仅使用包含文本所需的最小宽度。一个常见的用例是标准的段落系列。默认值
ui.TextHeightBehavior? textHeightBehavior
:具体用法查看drawParagraph的用法介绍
TextPainter和ParagraphBuilder差不多,绘制的文字默认颜色是白色,大小是14。所以这里我们跳过一些不必要的介绍,先绘制一个最基本的:
TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: const TextStyle(color: Colors.black)),
textDirection: TextDirection.ltr,
);
textPainter.layout(maxWidth: size.width / 3 * 2);
textPainter.paint(canvas, Offset(size.width / 3 / 2, 120));
可以看到,在TextPainter中,我们需要使用TextSpan组件来设置文字的内容和样式,关于这个组件的用法就不多做介绍了。
在TextPainter中,我们可以通过ellipsis来自定义显示文字溢出后的效果:
TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: const TextStyle(color: Colors.black)),
textDirection: TextDirection.ltr,
maxLines: 3,
ellipsis: "😀😁😂",
);
当然,我们可以在实例化的时候直接给属性赋值,也可以在实例化之后用.
和..
来给属性赋值。
// 方法一
TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: const TextStyle(color: Colors.black)),
textDirection: TextDirection.ltr,
);
// 方法二
TextPainter textPainter = TextPainter()
..text = TextSpan(text: text, style: const TextStyle(color: Colors.black))
..textDirection = TextDirection.ltr;
// 方法三
TextPainter textPainter = TextPainter();
textPainter.text = TextSpan(text: text, style: const TextStyle(color: Colors.black));
textPainter.textDirection = TextDirection.ltr;
方法
getWordBoundary
:具体用法查看drawParagraph的用法介绍getPositionForOffset
:具体用法查看drawParagraph的用法介绍getLineBoundary
:具体用法查看drawParagraph的用法介绍computeLineMetrics
:具体用法查看drawParagraph的用法介绍computeDistanceToActualBaseline
:返回从文本顶部到给定类型的第一个基线的距离getBoxesForSelection
:返回绑定给定选择的矩形列表getFullHeightForCaret
:{@template flutter.painting.textPainter.getFullHeightForCaret} 返回给定position字形的支柱边界高度getOffsetForCaret
:返回绘制插入符号的偏移量getOffsetAfter
:返回输入光标可以定位的offset量之后最近的偏移量getOffsetBefore
:返回输入光标可以定位的offset量之前最接近的偏移量markNeedsLayout
:将此文本绘制器的布局信息标记为脏并删除缓存信息。在引擎布局发生变化的情况下,使用此方法通知文本绘制器重新布局。在大多数情况下,更新框架中的文本绘制器属性会自动调用此方法setPlaceholderDimensions
:设置text中每个占位符的尺寸。提供的PlaceholderDimensions数量应与文本中PlaceholderSpan的数量相同。传入一个空值或空value将什么都不做。如果在未设置占位符尺寸的情况下尝试layout ,则占位符将在文本布局中被忽略,并且不会返回有效的inlinePlaceholderBoxeslayout
:计算字形的视觉位置以绘制文本paint
:在给定的偏移处将文本绘制到给定的画布上
computeDistanceToActualBaseline
-
TextBaseline.alphabetic
:用于对齐字母字符的字形底部的水平线textPainter.layout(maxWidth: size.width / 3 * 2); textPainter.paint(canvas, Offset(size.width / 3 / 2, 120)); double alphabetic = textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic);
-
TextBaseline.ideographic
:用于对齐表意字符的水平线double ideographic = textPainter.computeDistanceToActualBaseline(TextBaseline.ideographic);
getBoxesForSelection
该方法有以下3个属性:
TextSelection
:创建文本选择。关于该对象的用法请查看这里BoxHeightStyle boxHeightStyle
:返回绑定给定选择的矩形列表。boxHeightStyle和boxWidthStyle参数可用于选择TextBox的形状。如果此文本绘制器包含双向文本,则给定选择可能具有多个矩形,因为逻辑上连续的文本可能在视觉上不连续。默认是tightBoxWidthStyle boxWidthStyle
:同BoxHeightStyle
List<TextBox> textBox = textPainter.getBoxesForSelection(
const TextSelection(baseOffset: 4, extentOffset: 12),
boxHeightStyle: BoxHeightStyle.tight,
boxWidthStyle: BoxWidthStyle.tight,
);
试着修改boxHeightStyle的值,结果没有影响。
getFullHeightForCaret
该方法需要传入2个参数:
TextPosition position
:创建一个表示字符串中特定位置的对象Rect caretPrototype
:创建一个矩形
Rect caretPrototype = const Rect.fromLTWH(100, 100, 40, 120);
double? value = textPainter.getFullHeightForCaret(const TextPosition(offset: 24), caretPrototype);
getOffsetForCaret
同getFullHeightForCaret
markNeedsLayout
将这个文本绘制器的布局信息标记为dirty ,并删除缓存的信息
setPlaceholderDimensions
需要传入一个PlaceholderDimensions对象的列表。
PlaceholderDimensions
Size size
:占位符的宽度和高度尺寸PlaceholderAlignment alignment
:如何将占位符与文本对齐TextBaseline? baseline
:文本基线的位置double? baselineOffset
:基线与占位符上边缘的距离
const PlaceholderDimensions(
size: Size(100, 20),
alignment: PlaceholderAlignment.middle,
baseline: TextBaseline.alphabetic,
baselineOffset: 24,
)
提供的PlaceholderDimensions数量应与文本中PlaceholderSpan的数量相同。传入一个空值或空value将什么都不做。如果在未设置占位符尺寸的情况下尝试layout ,则占位符将在文本布局中被忽略,并且不会返回有效的inlinePlaceholderBoxes 。
不会🙄
layout
有2个可选参数:
double maxWidth
:文字显示的最大宽度double minWidth
:文字显示的最小宽度
textPainter.layout(maxWidth: size.width / 3 * 2);
paint
textPainter.paint(canvas, Offset(size.width / 3 / 2, 120));