天天看點

Flutter 32: 圖解 TextPainter 與 TextSpan 小嘗試

      大家在學習 Flutter 時一定會用過 Text,而對于一些複雜文本的處理可能會選擇 RichText ,再進一步,使用 RichText 就一定要用 TextSpan

,小菜本以為可以做為一個小知識點進行簡單學習,但是随着深入嘗試發現 TextSpan 涉及東西很多,很值得研究,是以單獨整理一篇小博文。

      RichText 富文本核心即 TextSpan,而 TextSpan 結構很像 Android 中的 ViewGroup 樹型結構。

RichText 日常用法

      小菜了解為 RichText 是進階版的 Text,如下直接看執行個體:

  1. 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);
}           
  1. 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);
}           
  1. 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 方法,即繪制位置與繪制範圍。

  1. 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));           
  1. 而對于繪制多效果的樣式也是很友善,與 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));           
  1. 小菜一直有問題的就是設定點選事件,小菜以為與 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));           
  1. 小菜認為最有意思的就是 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));           

      如果有不對的地方還希望多多指教!

繼續閱讀