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