【Flutter】TextField縦幅について
この記事は2024年のFlutter Advent Calendar(カレンダー 1)の9日目の記事です。
アドベントカレンダーという機会に、「とことん細かいことを調べてみよう」の記事になってますので、
はじめに(発端)
新規登録画面のデザインを再現しよう、となったとき、いざやってみるとフィールドの縦幅を合わせるのがうまくいかない、ということがあった。
例)
縦幅をデザインに合わせると、文字が半分隠れてしまう、など。
今回デフォルトの「縦幅」がどういったルールで決まっているのか、など、調査・動作確認した内容について記載する。
書いていること
- 何も指定しない場合、縦幅がどう決まっているかの仕組みの整理
- デザイナーに縦幅の決め方を連携する場合の情報
- 縦幅を比較的小さくした場合TextField・CustomDropdownButtonFormField・DropdownMenuの縦幅を合わせる方法
- ※ Flutterで固定値で設定されているものより小さいものを例とする。
検証環境
Flutter 3.24.5 • channel stable •
https://github.com/flutter/flutter.git
Framework • revision dec2ee5c1f (4 weeks ago) • 2024-11-13 11:13:06
-0800
Engine • revision a18df97ca5
Tools • Dart 3.5.4 • DevTools 2.37.3
動作確認をしたリポジトリ
縦幅はどうやって決まっているか
簡単のため、基本は「TextField」で説明しますが、プルダウンなども含めて要素は以下となります。
① 内部的固定値の大きさ
② 余白
③ FontSize × Line Height(1行の大きさ、単純にフォントが占有する縦幅)
① 内部的固定値の大きさ
これは「kMinInteractiveDimension」という変数名で「48」となります。
「これは、ユーザーが操作しにくい小さな領域を避けるために使われる。」と説明があります
(づだに教えてもらったものです、ありがとうございます)
デザイナーにはぜひ、この情報を伝えておいて「48」かそれより大きい縦幅で、
デザインを作ってもらうのがいいかと思います。
ソースコード的にもある設定をしない限りは、「48」より小さないフィールドを作ることはできない形になります。
② 余白
余白は「InputDecoration」の中のcontentPaddingのプロパティで設定します。
デフォルト値は以下のルールで決まるようです。
「Material 3
」での表示のため「Material 3
」の値が利用されています。
contentPaddingの値は「border : true」・「isDense : false」のため、
EdgeInsets.fromLTRB(12, 20, 12, 12)
の値のはずです(+32されている)
FontSizeを変えていった場合の縦幅の変化
ほぼデフォルトのままFontSize(8・16・32・64)だけを変更してくと以下のような画面になります。
Widget Inspectorにて確認した値をまとめると以下の通り。
FontSize | 縦幅 |
---|---|
8 | 48 |
16 | 56 |
32 | 80 |
32 | 128 |
SizedBoxで、上記「デフォルト」の縦幅より小さいものにすると、「文字が半分隠れてしまう」という現象が起きる。
例)
FontSize : 32 / 縦幅 : 80 となっていたものを、
SizedBoxでラップして縦幅 : 60とした場合。
「③ FontSize × Line Height(1行の大きさ、単純にフォントが占有する縦幅)」だけの状態
③の縦幅がどんなものであるか(簡単に)検証するために
まずは①②の影響を「0」にすることを考える。
①について
実はこの固定値は「InputDecoration」の設定値である「isDense」を「true」とすることである種、無効化される。
②について
contentPadding
をEdgeInsets.symmetric(horizontal: 8),
で上書きする(縦幅なし)
上記の設定とした場合、画面と値は以下のようになる。
FontSize | 縦幅 |
---|---|
8 | 12 |
16 | 24 |
32 | 48 |
64 | 96 |
縦幅/FontSize = 1.5となる。この値がLine Heightに相当する。
※今回の検証コード
return TextField(
style: TextStyle(
fontFamily: 'NotoSansJP',
fontWeight: FontWeight.w400,
fontSize: fontSize,
// height: 1,
),
controller: controller,
maxLength: maxLength,
maxLines: maxLines,
keyboardType: keyboardType,
decoration: InputDecoration(
// width : 1.0
border: const OutlineInputBorder(),
contentPadding: contentPadding,
isDense: isDende,
),
);
「Line Height」はTextStyleのheightで指定でき、
つまりはheight=1であれば、縦幅は理論上、fontSizeと同一になる。(詳しく書かないが、fontSizeと全く一緒だと「フォントメトリクス」の関係で不都合がある)
<ズレがあるが参考値にはなりそうだと思って書いている>「Line Height」の確認方法
「Line Height」はフォントごとに設定値が異なる。
「Flutterで実装して出力・計算する」という手もあるが、それだとデザイナーなど非エンジニアでは確認できないため、別の方法を考えた。
「フォントメトリクス」で計算する、というのが確実ではあるが、今回は簡易な確認としてFigmaを利用するものとする。
Figmaでテキストを入力して以下のように
・フォントを指定のもので設定
・フォントサイズを指定のもので設定
・line heightを「Auto」に設定
・layoutの「Auto height」を設定
→ これで上記赤枠のテキストボックスの縦幅が最終的FontSize × Line Height
の値となる、想定。
ただし、データを見返すとわかる通り、
FontSize : 32で実際「48」となったのが、Figmaだと「46」となる。
若干のズレがある状況
まとめ
新規登録画面の縦幅を決めるとき、
- 内部的固定値 48を下回らないことを念頭に置く
- フォントを決定(確認)する
- FontSizeを決定する
-
Line Height
について、各フォントのデフォルト値を確認するか、「Line Height」明示的に決定し「FontSize × Line Height
」の幅を確保する - 上下の余白をそれぞれどの程度確保するか決定する
上記考えておけば、綺麗な縦幅の入力項目が作成できるのではないかと思う。
発展
ここで、あえて「固定値 48」を下回った入力画面の作成を考える。
TextFieldと一緒に実装されがちな「DropdownMenu」や「DropdownButtonFormField」と縦幅を揃えて表示してみる。
内部的固定値 48を下回らないことを念頭に置く
→ 今回はあえて「48」よりも小さくなるように考える
フォントを決定(確認)する
→ Noto Sans JP
FontSizeを決定する
→ 「48」よりも小さくしたいので「18」とする
Line Height
について、各フォントのデフォルト値を確認するか、「Line Height」明示的に決定し「FontSize × Line Height
」の幅を確保する
→ 今回はFigmaで確認し「26」は必要なことがわかる。
上下の余白をそれぞれどの程度確保するか決定する
→ 上下に「8」確保する(「48」よりも小さくなるように)
縦幅が「42」となる
結果
ソースコード実装時の細かい工夫
以下のソースコードで「FontSize」や「isDense」・「contentPadding」などを調整した。
今回
FontSize : 18
isDense : true
contentPadding : contentPadding: EdgeInsets.symmetric(horizontal: 8,vertical: 8,)
一覧
TextField部分
DropdownButtonFormField部分
DropdownMenu
メモ
- 全体 : 若干のずれなどがあるのでSizedBoxでも「42」を指定している
- DropdownButtonではなくDropdownButtonFormFieldとしたのは
decoration
部分がTextField
と同じように扱えたから - DropdownMenuのみ、他と「isDense」などの考え方が異なりそうだった。縦幅は
inputDecorationTheme
の中のconstraints
で最大値・最小値を指定して小さくしている。
Discussion