Flutterで罫線付きのTextFieldのやり方

5 min読了の目安(約4700字TECH技術記事

How to draw underline multiline in TextField(ruled lines)

よくある大学ノートの横線みたいのが欲しかった

TextFieldにもTextStyleにもInputDecorationのプロパティにもいまいちそれっぽい項目が見つけられなかったため、CustomPaintで線を引くことにした。

参考にしました、ありがとうございます(Android)
http://tatete.blogspot.com/2012/03/edittext.html


欲しかった完成品


やり方

CustomPaintウィジェットとTextFieldウィジェットを重ねる

final globalKeyGetTextField = GlobalKey();

class RuledLineTextField extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Stack(
        children: <Widget>[
          CustomPaint(
            painter: TextUnderLinePainter(),// 次に説明する後ろの罫線書くウィジェット
          ),
          TextField(
            key: globalKeyGetTextField,
            keyboardType: TextInputType.multiline,
            maxLines: 1000,
            decoration: const InputDecoration(border: InputBorder.none),
          ),
        ],
      ),
    );
  }
}
  • globalKeyGetTextField → TextUnderLinePainter()側からTextFieldのサイズを知るために使用。
  • maxLines: 1000 → 罫線書く範囲を知っておく必要があるので行数は有限にする必要がある。1000じゃなくてもOK

CustomPaintで後ろの罫線を引く(今回はグレーで太さ1)

CustomPaint使う際に参考にしました。ありがとうございます。
https://www.egao-inc.co.jp/programming/flutter_custompainter_shape/
https://cbtdev.net/flutter-custompainter/

class TextUnderLinePainter extends CustomPainter {
  TextUnderLinePainter();

  
  void paint(Canvas canvas, Size size) {
    final textFieldRenderBox =
        globalKeyGetTextField.currentContext.findRenderObject() as RenderBox;

    final ruledLineWidth = textFieldRenderBox.size.width;
    final ruledLineSpace = textFieldRenderBox.size.height / 1000;
    const ruledLineContentPadding = 12;

    //1000行分の線を引く
    final paint = Paint()
      ..color = Colors.grey
      ..strokeWidth = 1;
    for (var i = 1; i <= 1000; i++) {
      canvas.drawLine(
          Offset(0, ruledLineSpace * i + ruledLineContentPadding),
          Offset(ruledLineWidth, ruledLineSpace * i + ruledLineContentPadding),
          paint);
    }
  }

  
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

final textFieldRenderBox =
globalKeyGetTextField.currentContext.findRenderObject() as RenderBox;
→これで描画されているテキストフィールドが取得できる。RenderObject型(抽象クラス)で取得されるのでas RenderBoxでキャストして使う。

final ruledLineSpace = textFieldRenderBox.size.height / 1000; → TextFieldの高さをmaxLinesの値で割ることで段落1行分の高さを求めてる。(今回は1000)

const ruledLineContentPadding = 12; → テキストフィールドにはデフォルトで上部分に12のパディングがつくため(InputDecoration)、最初の罫線の位置もこれに合わせる必要がある。
デフォルトでTextFieldについてるPadding

完成

コード全体

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Ruled Line TextField'),
        ),
        body: RuledLineTextField(),
      ),
    );
  }
}

final globalKeyGetTextField = GlobalKey();

class RuledLineTextField extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Stack(
        children: <Widget>[
          CustomPaint(
            painter: TextUnderLinePainter(),
          ),
          TextField(
            key: globalKeyGetTextField,
            keyboardType: TextInputType.multiline,
            maxLines: 1000,
            decoration: const InputDecoration(border: InputBorder.none),
          ),
        ],
      ),
    );
  }
}

class TextUnderLinePainter extends CustomPainter {
  TextUnderLinePainter();

  
  void paint(Canvas canvas, Size size) {
    final textFieldRenderBox =
        globalKeyGetTextField.currentContext.findRenderObject() as RenderBox;

    final ruledLineWidth = textFieldRenderBox.size.width;
    final ruledLineSpace = textFieldRenderBox.size.height / 1000;
    const ruledLineContentPadding = 12;

    final paint = Paint()
      ..color = Colors.grey
      ..strokeWidth = 1;
    for (var i = 1; i <= 1000; i++) {
      canvas.drawLine(
          Offset(0, ruledLineSpace * i + ruledLineContentPadding),
          Offset(ruledLineWidth, ruledLineSpace * i + ruledLineContentPadding),
          paint);
    }
  }

  
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

正直無理やり感があるので(Stackで重ねてる辺り)、TextField周りだけをいじって線を引く方法あったら誰か教えて...