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));

image

绘制的文字默认大小为14,颜色为白色。如果你直接使用以上代码并不能绘制成我这样。这里我用SizedBox.expand包裹了CustomPaint,否则你的size值为0,ParagraphConstraints(width: size.width)设置的也是0,什么都绘制不出来。当然,你也可以使用Center包裹CustomPaint,需要重新设置一下offset,显示出来的效果如下:

canvas.drawParagraph(paragraph, const Offset(0, -100));

image

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 时,将使用字体的默认上升。默认为true
  • bool applyHeightToLastDescent:当为 true 时, TextStyle.height修饰符将应用于最后一行的下降。当为 false 时,将使用字体的默认下降。默认为true
  • TextLeadingDistribution 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这个值对文字的排版有影响,其他两个属性没有效果。可能和字体有关,这个属性一般也用不到,默认就好。

image

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,则第一行的总上升将是 fontFamilyAscent + half-leading(height +leading) * fontSize 的最小值。否则,将由第一个文本的 Ascent + half-leading 决定。默认为 false

image

方法

  • 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));

image

注意:pushStyle中的设置会覆盖ParagraphStyleaddText必须放在后面。

addPlaceholder
paragraphBuilder.addPlaceholder(100, 80, PlaceholderAlignment.top);

image

PlaceholderAlignment还有 aboveBaselinebaselinebelowBaseline几个枚举值,这几个值的需要设置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));

image

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。否则为false
  • ascent:以 baseline 为界线,上部分的高
  • descent:以 baseline 为界线,下部分的高
  • unscaledAscent:忽略TextStyle设置的height,上部分的高
  • height:从顶部边缘到底部边缘的线的总高度
  • width:从最左侧字形的左边缘到最右侧字形的右边缘的线宽
  • left:线条左边缘的 x 坐标
  • baseline:此行从段落顶部开始的基线 y 坐标
  • lineNumber:行在整个段落中的编号,第一行索引为零

image

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),
]

image

getLineBoundary

用来获取当前索引在绘制文字的第几行的范围

该方法需要传入一个TextPosition对象,TextPosition有两属性:

  • offset :索引
  • affinity:关于 affinity 的介绍可以查看这里
TextRange textRange = paragraph.getLineBoundary(const TextPosition(offset: 10));
TextRange(start: 8, end: 16)

image

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)
]

image

getPositionForOffset

返回离坐标最近的字符串的索引

TextPosition textPosition = paragraph.getPositionForOffset(const Offset(100, 20));
TextPosition(offset: 3, affinity: TextAffinity.downstream)

image

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前不能为null
  • TextAlign textAlign:文本对齐方式。默认为TextAlign.start
  • TextDirection? textDirection:文字方向。虽然是可选,但是在调用layout前不能为null
  • double textScaleFactor:文字放大倍数。默认为1.0
  • int? maxLines:最多是多少行
  • String? ellipsis:使用省略号表示文本已溢出
  • Locale? locale:用于选择区域特定字形的语言环境
  • StrutStyle? strutStyle:具体用法查看drawParagraph的用法介绍
  • TextWidthBasis textWidthBasis:测量一行或多行文字宽度的不同方法
    • TextWidthBasis.longestLine:宽度将恰好足以包含最长的线并且不再。一个常见的用例是聊天气泡
    • TextWidthBasis.parent:多行文本将占据父级给出的全宽。对于单行文本,仅使用包含文本所需的最小宽度。一个常见的用例是标准的段落系列。默认值
  • ui.TextHeightBehavior? textHeightBehavior:具体用法查看drawParagraph的用法介绍

TextPainterParagraphBuilder差不多,绘制的文字默认颜色是白色,大小是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));

image

可以看到,在TextPainter中,我们需要使用TextSpan组件来设置文字的内容和样式,关于这个组件的用法就不多做介绍了。

TextPainter中,我们可以通过ellipsis来自定义显示文字溢出后的效果:

TextPainter textPainter = TextPainter(
  text: TextSpan(text: text, style: const TextStyle(color: Colors.black)),
  textDirection: TextDirection.ltr,
  maxLines: 3,
  ellipsis: "😀😁😂",
);

image

当然,我们可以在实例化的时候直接给属性赋值,也可以在实例化之后用...来给属性赋值。

// 方法一
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 ,则占位符将在文本布局中被忽略,并且不会返回有效的inlinePlaceholderBoxes
  • layout:计算字形的视觉位置以绘制文本
  • 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);
    

image

getBoxesForSelection

该方法有以下3个属性:

  • TextSelection:创建文本选择。关于该对象的用法请查看这里
  • BoxHeightStyle boxHeightStyle:返回绑定给定选择的矩形列表。boxHeightStyle和boxWidthStyle参数可用于选择TextBox的形状。如果此文本绘制器包含双向文本,则给定选择可能具有多个矩形,因为逻辑上连续的文本可能在视觉上不连续。默认是tight
  • BoxWidthStyle boxWidthStyle:同BoxHeightStyle
List<TextBox> textBox = textPainter.getBoxesForSelection(
  const TextSelection(baseOffset: 4, extentOffset: 12),
  boxHeightStyle: BoxHeightStyle.tight,
  boxWidthStyle: BoxWidthStyle.tight,
);

image

试着修改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));
posted @ 2022-04-03 21:57  菠萝橙子丶  阅读(3068)  评论(2编辑  收藏  举报