📖

【Flutter/Dart】自動改行Text(Field)について

2022/06/26に公開

背景

そういえば公式で自動でmaxLinesが変更されるようなWidgetなさそうだなと感じてちょっといろいろ調べてみました。

調査

まずはライブラリ

https://pub.dev/packages/auto_size_text

これは決まったサイズの枠に対して、自動で枠に合わせたサイズ調整をしてくれるライブラリだ。

https://api.flutter.dev/flutter/widgets/Flexible-class.html

これも上記に似ているかんじだ。
決まったサイズ内で自動改行をしてくれるように組むことができる。

結果、自分が求めているやつはなかった。(もう少し調べらたあるかも)

作っちゃおう!!

ということで作ってみた。
ここで作る際に必要な値はTextが入るWidgetのWidthと中に入るText自体の長さだ。

ここでヒントになったのは、TextのオプションであるTextOverflowの仕組みだ。
中のロジックを解析すると以下の2つで鍵になっていることがわかった。

GlobalObjectKey
TextPainter

https://stackoverflow.com/questions/50115416/get-height-of-a-widget-using-its-globalkey-in-flutter

https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/painting/text_painter.dart

GlobalObjectKey

final _globalkey = const GlobalObjectKey('text');

Text(
      key: _globalkey,
      ・・・
      ),
      
void _checkWidth() {
  final box = _globalkey.currentContext; // ここでグルーバルキーを設定したWidgetのデータの情報を取得可能
  if (box != null) {
     final data = box.findRenderObject(); // これでrendaring情報を取得できます。
      if (data is RenderBox) { // abstractくらすなのでisでクラスを指定
        final _localWidth = data.size.width; //これでWidthを取得
      }
  }
  
}

TextPainter

final textSpan = TextSpan(
          text: 'なにかの文字', // Widthを知りたい文字
          style: textStyle, // Widthを計測する種のスタイル
        );
final textPainter = TextPainter(
          text: textSpan,
          textDirection: TextDirection.ltr, // 右始まりか左始まりか
          maxLines: _maxLine, //ここでも行数を指定。
        )..layout(); // layoutでビルドして、サイズなを取得できるようになる。
final _localTextWidth = textPainter.size.width; //ここでテキストのWidthを取得可能

合体!!

// StatefulWidget前提です。
int _maxLine = 1; // これをText,TextField,EditableTextなどのmaxLinesに設定
final _limitLine = 3;
final _globalkey = const GlobalObjectKey('text');
final _diffList = <int>[0]; // 改行時に余る、差分Widthを格納していく

・・・

// _multiLineCheckをlistenerに入れて監視すれば自動で改行されます。
void _multiLineCheck() {
    final theme = Theme.of(context);
    final word = ''; // TextEditControllerやTextに入れる値を格納
    final textStyle = TextStyle();
    final box = _globalkey.currentContext;
    final exWidth = _diffList.reduce((a, b) => a + b) - _diffList.last;

    if (word.isEmpty) {
      setState(() {
        _maxLine = 1;
        _diffList
          ..clear()
          ..add(0);
      });
      return;
    }
    if (box != null) {
      final data = box.findRenderObject();
      if (data is RenderBox) {
        final textSpan = TextSpan(
          text: word,
          style: textStyle,
        );
        final textPainter = TextPainter(
          text: textSpan,
          textDirection: TextDirection.ltr,
          maxLines: _maxLine,
        )..layout();
	// 1行目の処理
        if (_maxLine == 1) {
          if (textPainter.size.width >= data.size.width * _maxLine) {
            setState(() {
              _maxLine++;
              _diffList.add(0);
            });
          } else {
            _diffList[_maxLine - 1] =
                (data.size.width - textPainter.size.width).toInt();
          }
	// 2行目から_limitLineまでの処理
        } else if (_maxLine > 1 && _maxLine < _limitLine) {
          if (textPainter.size.width >=
              (data.size.width * _maxLine) - exWidth) {
            setState(() {
              _maxLine++;
              _diffList.add(0);
            });
          } else if (textPainter.size.width <=
              (data.size.width * (_maxLine - 1))) {
            setState(() {
              _maxLine--;
              _diffList.removeLast();
            });
          } else {
            _diffList[_maxLine - 1] = ((data.size.width * _maxLine) -
                    textPainter.size.width -
                    exWidth)
                .toInt();
          }
	// _limitLine移行の処理
        } else {
          if (textPainter.size.width <=
              (data.size.width * (_maxLine - 1) - exWidth)) {
            setState(() {
              _maxLine--;
              _diffList.removeLast();
            });
          }
        }
      }
    }
  }

・・・

作ってみて

公式APIの深堀とかあまりしないのでいろいろ勉強になった。
また、なにかお題が思いついたら深堀してみようかと思います。

Discussion