Flutter 32: 圖解 TextPainter 與 TextSpan 小嚐試
大家在學習 Flutter 時一定會用過 Text ,而對於一些複雜文字的處理可能會選擇 RichText ,再進一步,使用 RichText 就一定要用 TextSpan ,小菜本以為可以做為一個小知識點進行簡單學習,但是隨著深入嘗試發現 TextSpan 涉及東西很多,很值得研究,因此單獨整理一篇小博文。
RichText富文字核心即 TextSpan ,而 TextSpan 結構很像 Android 中的 ViewGroup 樹型結構。

TextSpan 樹形結構.png

RichText 日常用法
小菜理解為 RichText 是進階版的 Text ,如下直接看例項:
- TextDirection 用來控制文字位置,居左或居右邊;當與 TextAlign 屬性共存時,優先看整體,以 TextAlign 為準;
Widget richTextWid01() { return RichText( text: TextSpan( text: 'TextDirection.ltr 文字預設居左', style: TextStyle(fontSize: 16.0, color: Colors.black)), textDirection: TextDirection.ltr); } Widget richTextWid02() { return RichText( text: TextSpan( text: 'TextDirection.rtl 文字預設居右', style: TextStyle(fontSize: 16.0, color: Colors.black)), textDirection: TextDirection.rtl); } Widget richTextWid03() { return RichText( text: TextSpan( text: 'textDirection 與 textAlign 同時設定,優先看整體,文字居中', style: TextStyle(fontSize: 16.0, color: Colors.black)), textDirection: TextDirection.rtl, textAlign: TextAlign.center); }
- RichText 可以藉助 TextSpan 實現文字的多種效果,小菜認為有點像文字效果拼接,每個 TextSpan 可以設定單獨效果;
Widget richTextWid04() { return RichText( text: TextSpan( text: '多種樣式,如:', style: TextStyle(fontSize: 16.0, color: Colors.black), children: <TextSpan>[ TextSpan( text: '紅色', style: TextStyle(fontSize: 18.0, color: Colors.red)), TextSpan( text: '綠色', style: TextStyle(fontSize: 18.0, color: Colors.green)), TextSpan( text: '藍色', style: TextStyle(fontSize: 18.0, color: Colors.blue)), TextSpan( text: '白色', style: TextStyle(fontSize: 18.0, color: Colors.white)), TextSpan( text: '紫色', style: TextStyle(fontSize: 18.0, color: Colors.purple)), TextSpan( text: '黑色', style: TextStyle(fontSize: 18.0, color: Colors.black)) ]), textAlign: TextAlign.center); }
- TextSpan 可以藉助 recognizer 設定點選事件,包括點選/長按等;
final TapGestureRecognizer recognizer = TapGestureRecognizer(); void initState() { super.initState(); recognizer.onTap = () { Toast.show('您好,歡迎點贊或關注!', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM); }; } Widget richTextWid05() { return RichText( text: TextSpan( text: 'recognizer 為手勢識別者,可設定點選事件,', style: TextStyle(fontSize: 17.0, color: Colors.black), children: <TextSpan>[ TextSpan( text: '點我試試', style: TextStyle(fontSize: 17.0, color: Colors.blue), recognizer: recognizer) ])); }
TextPainter 日常用法
RichText的使用很方便,但如果在深入瞭解 TextSpan 就有很多趣味了; Flutter 提供了和 Android 類似的 Canvas 繪製方法,但是 Canvas 卻不支援 drawText ,如果想要實現繪製文字,就需要 TextPainter 而其內部主要是由 TextSpan 實現。
使用 TextPainter 時需要繼承 CustomPainter ,並實現 paint 和 shouldRepaint 方法,主要是在 paint 中進行繪製 TextPainter 。與 RichText 功能相同,可以完全實現 RichText 效果;
TextPainter繪製需要實現 layout 與 paint 方法,即繪製位置與繪製範圍。
- TextDirection 和 TextAlign 效果與 RichText 一致,但是 TextPainter 繪製時需要設定 layout 的最大最小範圍,而此時,文字位置與 layout 有關;當文字長度小於設定的 minWidth 最小寬度時,以 minWidth 寬度為限制居左/居右/居中等;而當文字長度大於設定的 minWidth 最小寬度時,以 maxWidth 最大寬度為限制,包括換行等;
TextPainter( text: TextSpan( text: 'TextDirection.ltr 文字預設居左', style: TextStyle(fontSize: 16.0, color: Colors.black)), textDirection: TextDirection.ltr) ..layout(maxWidth: Screen.width, minWidth: Screen.width) ..paint(canvas, Offset(0.0, 0.0)); TextPainter( text: TextSpan( text: 'TextDirection.rtl 文字預設居右', style: TextStyle(fontSize: 16.0, color: Colors.black)), textDirection: TextDirection.rtl) ..layout(maxWidth: Screen.width, minWidth: Screen.width) ..paint(canvas, Offset(0.0, 24.0)); TextPainter( text: TextSpan( text: 'textDirection 與 textAlign 同時設定,優先看整體,文字居中', style: TextStyle(fontSize: 16.0, color: Colors.black)), textDirection: TextDirection.rtl, textAlign: TextAlign.center) ..layout(maxWidth: Screen.width, minWidth: Screen.width) ..paint(canvas, Offset(0.0, 48.0)); TextPainter( text: TextSpan( text: '文字位置與 layout 的最大最小寬度有關', style: TextStyle(fontSize: 16.0, color: Colors.black)), textDirection: TextDirection.rtl) ..layout(maxWidth: Screen.width - 100.0, minWidth: Screen.width - 200.0) ..paint(canvas, Offset(0.0, 90.0)); TextPainter( text: TextSpan( text: '文字長度較小', style: TextStyle(fontSize: 16.0, color: Colors.black)), textDirection: TextDirection.rtl) ..layout(maxWidth: Screen.width - 100.0, minWidth: Screen.width - 140.0) ..paint(canvas, Offset(0.0, 124.0));
- 而對於繪製多效果的樣式也是很方便,與 RichText 基本一致;
TextPainter( text: TextSpan( text: '多種樣式,如:', style: TextStyle(fontSize: 16.0, color: Colors.black), children: <TextSpan>[ TextSpan( text: '紅色', style: TextStyle(fontSize: 18.0, color: Colors.red)), TextSpan( text: '綠色', style: TextStyle(fontSize: 18.0, color: Colors.green)), TextSpan( text: '藍色', style: TextStyle(fontSize: 18.0, color: Colors.blue)), TextSpan( text: '白色', style: TextStyle(fontSize: 18.0, color: Colors.white)), TextSpan( text: '\n紫色', style: TextStyle(fontSize: 18.0, color: Colors.purple)), TextSpan( text: '黑色', style: TextStyle(fontSize: 18.0, color: Colors.black)) ]), textDirection: TextDirection.ltr, textAlign: TextAlign.center) ..layout(maxWidth: Screen.width, minWidth: Screen.width) ..paint(canvas, Offset(0.0, 148.0));
- 小菜一直有問題的就是設定點選事件,小菜以為與 RichText 一樣直接傳遞 recognizer 即可,但始終無法調起,希望有解決過這個問題的朋友多多指導,如下是小菜的測試程式碼;
TextPainter( text: TextSpan( text: 'recognizer 為手勢識別者,可設定點選事件,', style: TextStyle(fontSize: 17.0, color: Colors.black), children: <TextSpan>[ TextSpan( text: '測試暫時有誤,待研究', style: TextStyle(fontSize: 17.0, color: Colors.blue)) ], recognizer: TapGestureRecognizer() ..onTap = () { print('===測試暫時有誤,待研究=='); }), textDirection: TextDirection.ltr, textAlign: TextAlign.center) ..layout(maxWidth: Screen.width - 40.0, minWidth: Screen.width - 40.0) ..paint(canvas, Offset(20.0, 200.0));
- 小菜認為最有意思的就是 TextSpan 中 style 的 height 屬性,在 TextSpan 中此值設定行高,是以文字基準線為最小距離;
TextPainter( text: TextSpan( text: 'TextPainter 小嚐試', style: TextStyle(fontSize: 20.0, color: Colors.black54), children: <TextSpan>[ TextSpan( text: '\n作者:和尚(height:1.6)', style: TextStyle( fontSize: 14.0, color: Colors.black54, height: 1.6)) ]), textDirection: TextDirection.ltr, textAlign: TextAlign.center) ..layout(maxWidth: Screen.width, minWidth: Screen.width) ..paint(canvas, Offset(0.0, 20.0)); TextPainter( text: TextSpan( text: 'TextPainter 小嚐試', style: TextStyle(fontSize: 20.0, color: Colors.black54), children: <TextSpan>[ TextSpan( text: '\n作者:和尚(height:3.0)', style: TextStyle( fontSize: 14.0, color: Colors.black54, height: 3.0)) ]), textDirection: TextDirection.ltr, textAlign: TextAlign.center) ..layout(maxWidth: Screen.width, minWidth: Screen.width) ..paint(canvas, Offset(0.0, 90.0)); TextPainter( text: TextSpan( text: 'TextPainter 小嚐試(height:0.1)', style: TextStyle(fontSize: 20.0, color: Colors.black54, height: 0.1), children: <TextSpan>[ TextSpan( text: '\nTextPainter 小嚐試(height:0.1)', style: TextStyle( fontSize: 20.0, color: Colors.black54, height: 0.1)), TextSpan( text: '\nTextPainter 小嚐試(height:0.1)', style: TextStyle( fontSize: 20.0, color: Colors.black54, height: 0.1)) ]), textDirection: TextDirection.ltr, textAlign: TextAlign.center) ..layout(maxWidth: Screen.width, minWidth: Screen.width) ..paint(canvas, Offset(0.0, 220.0));

如果有不對的地方還希望多多指出。以下是小菜公眾號,歡迎閒來吐槽~

公眾號.jpg