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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM