【Flutter】TextFieldに横罫線を表示させる方法(Widgetなど)はあるのでしょうか?

Dart

1import 'package:flutter/material.dart';2 3void main() => runApp(MyApp());4 5// Text Widgetの位置把握のためGlobalKey型のグローバル変数を定義6GlobalKey globalKeyTextWidgetO = GlobalKey();7 8class MyApp extends StatelessWidget {9 10 Widget build(BuildContext context) {11 return MaterialApp(12 title: "Test",13 theme: ThemeData.light(),14 home: SampleScreenO(),15 );16 }17}18 19class SampleScreenO extends StatefulWidget {20 21 _SampleScreenOState createState() => _SampleScreenOState();22}23 24class _SampleScreenOState extends State<SampleScreenO> {25 26 // Text Widgetの2次元情報を入れるためのRenderBox型の変数を定義27 // null safety対応でlateをつける28 late RenderBox textWidgetO;29 30 // Text WidgetのY座標開始位置と高さ(縦幅)を入れる変数を定義31 // 初回ビルド時のnullエラーを防ぐため、初期値を設定しておく32 double textWidgetDyO = 0;33 double textWidgetHeightO = 0;34 35 // 画面全体のパディング、フォントサイズ、1行の高さを決める係数を設定36 static const double standardPaddingO = 8.0;37 static const double fontSizeO = 18.0;38 static const double lineSpaceO = 1.2;39 40 // TextField上にデフォルトで設定される上部パディングの変更値。ここでは0に設定41 static const double contentPaddingO = 0.0;42 43 // TextFieldに設置するFocusNodeのインスタンス作成44 FocusNode focusNodeTextFieldO = FocusNode();45 46 // 初期値設定等を行うinitStateメソッドを導入47 48 void initState() {49 50 // Text Widgetが描画された後でないとGlobalKeyの値が取得できずエラーになるため、51 // buildメソッド完了後にGlobalKeyの値取得が行われるよう、52 // addPostFrameCallbackメソッドを使用53 // null safety対応で?(null以外のみアクセス)をつける54 WidgetsBinding.instance?.addPostFrameCallback((cb) {55 56 // GlobalKeyを通じてText Widgetの2次元情報を取得57 // null safety対応で?と最後にas RenderBoxをつける58 textWidgetO = globalKeyTextWidgetO.currentContext?.findRenderObject() as RenderBox;59 60 // 2次元情報からText Widgetの縦方向の上端位置(Y座標)と高さ(縦幅)を取得61 textWidgetDyO = textWidgetO.localToGlobal(Offset.zero).dy;62 textWidgetHeightO = textWidgetO.size.height;63 64 // 確認のため、取得した位置と高さをDebugウィンドウに表示65 print("TextWidgetの上端位置 $textWidgetDyO、TextWidgetの高さ $textWidgetHeightO");66 67 // Text Widgetの位置と高さを取得後、setStateメソッドで全体を再描画する68 setState(() {});69 });70 71 super.initState();72 }73 74 75 Widget build(BuildContext context) {76 77 // 画面全体の縦幅と横幅を取得するためのMediaQuery.ofメソッド78 // ※画面の縦幅・横幅は変化しないためfinalで宣言79 final screenHeightO = MediaQuery.of(context).size.height;80 final screenWidthO = MediaQuery.of(context).size.width;81 82 // 画面の下端位置(キーボード出現時はキーボードの上端位置)を取得するためのMediaQuery.ofメソッド83 var textFieldBottomO = MediaQuery.of(context).viewInsets.bottom;84 85 // TextFieldの縦幅を柔軟に設定する計算式86 // 画面全体に設定している下部パディングも引く必要あり87 var textFieldVerticalRangeO=88 screenHeightO 89 - (textWidgetDyO + textWidgetHeightO)90 - textFieldBottomO 91 - standardPaddingO;92 93 return Scaffold(94 appBar: AppBar(95 title: Text("TestApp"),96 ),97 body: Padding(98 padding: const EdgeInsets.all(standardPaddingO),99 child: Column(100 children: [101 Text(102 "↓TextField",103 104 // GlobalKeyをText Widgetのkeyプロパティに設定105 key: globalKeyTextWidgetO,106 107 ),108 Container(109 110 // 分かりやすくするために枠線を付けるが、内側に自動で1.0のパディングが発生する点に注意111 decoration: BoxDecoration(112 border: Border.all(color: Colors.grey),113 ),114 115 // 初回描画時点では、GlobalKey未取得のため、計算式から高さを求めると116 // 画面サイズオーバーのエラーになる。これを回避するため、GlobalKeyの取得が117 // 未了の段階(=初回描画時点)では高さを0.0にしておく118 height: (textWidgetDyO == 0)119 ? 0.0120 : textFieldVerticalRangeO,121 122 child: Scrollbar(123 124 // スクロール時に罫線も動かすために、SingleChildScrollViewが必要125 child: SingleChildScrollView(126 127 // Stackを使って、罫線を引くCustomPaintの上に、TextFieldを重ね表示する128 child: Stack(129 children: <Widget>[130 131 // 描画するCustomPaintクラスの呼び出し132 CustomPaint(133 134 // painter属性では、外部に作成したクラスに処理を飛ばす135 // 画面の座標・幅情報を引数として渡す136 painter: DisplayRuledLinesO(137 textFieldVerticalRangeO: textFieldVerticalRangeO,138 screenWidthO: screenWidthO,139 focusNodeTextFieldO: focusNodeTextFieldO,140 textWidgetDyO: textWidgetDyO,141 textWidgetHeightO: textWidgetHeightO,142 ),143 ),144 145 TextField(146 147 // FocusNodeのインスタンスを設置148 focusNode: focusNodeTextFieldO,149 150 // フォントサイズを設定151 style: TextStyle(fontSize: fontSizeO),152 153 strutStyle: const StrutStyle(154 155 // 1行の高さをフォントサイズに対する倍数値で設定156 height: lineSpaceO,157 158 // 改行とともにズレるのを防ぐため、行間強制を設定159 forceStrutHeight: true,160 161 ),162 163 autofocus: true,164 165 decoration: InputDecoration(166 167 // TextFieldの上端から書き始められるように設定168 isDense: true,169 170 border: InputBorder.none,171 172 // デフォルトの上部パディング(12.0)を変更するために設定173 contentPadding:174 const EdgeInsets.only(top: contentPaddingO),175 ),176 177 keyboardType: TextInputType.multiline,178 179 // 行数は上限なしで設定180 maxLines: null,181 ),182 ],183 ),184 ),185 ),186 ),187 ],188 ),189 ),190 );191 }192}193 194 195// 横罫線の描画処理をするCustomPainterの拡張クラス196class DisplayRuledLinesO extends CustomPainter {197 198 final double textFieldVerticalRangeO;199 final double screenWidthO;200 final FocusNode focusNodeTextFieldO;201 final double textWidgetDyO;202 final double textWidgetHeightO;203 204 // 引数から初期値を受け取るコンストラクタ205 DisplayRuledLinesO({206 required this.textFieldVerticalRangeO,207 required this.screenWidthO,208 required this.focusNodeTextFieldO,209 required this.textWidgetDyO,210 required this.textWidgetHeightO,211 });212 213 214 215 216 void paint(Canvas canvas, Size size) {217 218 // 罫線を描画する範囲(高さ)を計算219 // 見えているTextFieldの高さ(キーボードの出現有無で可変)に、スクロールで上に動いた分を加算220 // これにより、スクロールとともに追加で罫線を描画し、罫線が無くなることを防ぐ221 final drawingVerticalRangeO =222 223 // TextFieldの縦幅224 textFieldVerticalRangeO 225 + (226 // GlobalKeyのY座標位置を加算227 // Containerに枠線設定をすることで発生する上部パディング1.0を加える228 // ※枠線を無くしたときは、この「+1.0」は削除が必要229 // さらに、TextField上部のデフォルトのパディングを変更した値が、0より大きいときは、その加算も必要230 (textWidgetDyO + textWidgetHeightO + 1.0 + _SampleScreenOState.contentPaddingO)231 232 // FocusNodeのY座標位置(スクロールによる移動をトレースしたTextField上端位置)を減算233 - focusNodeTextFieldO.offset.dy 234 );235 236 // 確認のため、Debugウィンドウに表示237 print("TextFieldの縦幅: $textFieldVerticalRangeO");238 print("GlobalKeyの位置: ${textWidgetDyO + textWidgetHeightO + 1.0 + _SampleScreenOState.contentPaddingO}");239 print("FocusNodeの位置: ${focusNodeTextFieldO.offset.dy}");240 241 // フォントサイズ×行間係数(strutStyleで規定)で1行の高さを算出し(ズレの蓄積を防ぐため四捨五入して整数化する)242 // ※切上げではなく四捨五入に修正(2021.12.26修正)243 // 上で求めた描画範囲(高さ)を割って、描画本数を計算する(切捨て)244 final ruledLineSpaceO = (_SampleScreenOState.fontSizeO * _SampleScreenOState.lineSpaceO).round();245 print("ruledLineSpaceO: $ruledLineSpaceO");246 final int ruledLineNumberO = (drawingVerticalRangeO / ruledLineSpaceO).floor();247 print("ruledLineNumberO: $ruledLineNumberO");248 249 // 罫線の横幅を計算250 // MediaQueryで把握済のTextFieldの幅から、左右に設定したパディングを引いて計算251 final ruledLineWidthO = screenWidthO - _SampleScreenOState.standardPaddingO * 2;252 253 // 罫線の描画254 final paint = Paint()255 ..color = Colors.grey 256 ..strokeWidth = 0.7;257 for (var i = 1; i <= ruledLineNumberO; i++) {258 canvas.drawLine(259 260 // Offset(x, y)からOffset(x', y')へ線を引き、それを「ruledLineNumberO」回繰り返す261 // TextField上部のデフォルトのパディングを変更した値が、0より大きいときは、Y座標に加算する必要あり262 Offset(0, ruledLineSpaceO * i + _SampleScreenOState.contentPaddingO),263 Offset(ruledLineWidthO, ruledLineSpaceO * i + _SampleScreenOState.contentPaddingO),264 paint,265 );266 }267 }268 269 // 一度描画した横罫線の再描画は不要なため、false270 271 bool shouldRepaint(CustomPainter oldDelegate) => false;272 273}

コメントを投稿

0 コメント