日益努力,而后风生水起|

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

本文作者:菠萝橙子丶

本文链接:https://www.cnblogs.com/ilgnefz/p/16097545.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   菠萝橙子丶  阅读(3141)  评论(2编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 Shining For One Thing 赵贝尔
Shining For One Thing - 赵贝尔
00:00 / 00:00
An audio error has occurred.

Shining For One Thing (《一闪一闪亮星星》影视剧歌曲) - 赵贝尔

词:萨吉

曲:金大洲

编曲:金大洲D-Jin

制作人:金大洲D-Jin

吉他/Bass:D-Jin

合声编写/合声:赵贝尔

人声录音/编辑:张德龙@D-Jin Music Studio

混音/母带处理:George Dum

宣传推广:杨慧颖/杨佩

封面设计:HOO

OP/音乐制作出品:D-Jin Music(北京翊辰文化传媒有限公司)

(未经著作权人许可,不得翻唱、翻录或使用)

夏夜的花火

夏夜的花火

因为你在身边而深刻

因为你在身边而深刻

幸运的是我

在宇宙之间听见承诺

在宇宙之间听见承诺

嗨 是我

这一次别再错过

这一次别再错过

喜欢你该由我主动了

喜欢你该由我主动了

星星那么多

星星那么多

有数不尽的浪漫闪烁

注定这一颗

会让你刻在手臂左侧

属于我

星形心率的贴合

幸有你总在守护我

幸有你总在守护我

I fall in love

I fall in love

I see your love

遇见你才发现我在

等你到来

等你到来

Fallen star

The wonder of you

我会永远在你天空

我会永远在你天空

为你闪烁 my love

为你闪烁 my love

Shining for one thing

Shining for one thing

Shining for one thing

Shining for one thing

It's you

It's you

星星那么多

星星那么多

有数不尽的浪漫闪烁

注定这一颗

会让你刻在手臂左侧

属于我

星形心率的贴合

幸有你总在守护我

幸有你总在守护我

I fall in love

I fall in love

I see your love

遇见你才发现我在

等你到来

等你到来

Fallen star

The wonder of you

我会永远在你天空

我会永远在你天空

为你闪烁 my love

为你闪烁 my love

Shining for one thing

Shining for one thing

Shining for one thing

Shining for one thing

It's you

It's you