📘

【Flutter】TextField縦幅について

2024/12/09に公開

この記事は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

動作確認をしたリポジトリ

https://github.com/beeeyan/flutter_app_sample/tree/feature/vertical

縦幅はどうやって決まっているか

簡単のため、基本は「TextField」で説明しますが、プルダウンなども含めて要素は以下となります。

① 内部的固定値の大きさ
② 余白
③ FontSize × Line Height(1行の大きさ、単純にフォントが占有する縦幅)

① 内部的固定値の大きさ

これは「kMinInteractiveDimension」という変数名で「48」となります。
https://github.com/flutter/flutter/blob/3f08b617848e26b462560601c4a8b89a93515e77/packages/flutter/lib/src/material/constants.dart#L27

「これは、ユーザーが操作しにくい小さな領域を避けるために使われる。」と説明があります
づだに教えてもらったものです、ありがとうございます)

デザイナーにはぜひ、この情報を伝えておいて「48」かそれより大きい縦幅で、
デザインを作ってもらうのがいいかと思います。

ソースコード的にもある設定をしない限りは、「48」より小さないフィールドを作ることはできない形になります。

② 余白

余白は「InputDecoration」の中のcontentPaddingのプロパティで設定します。
デフォルト値は以下のルールで決まるようです。

Material 3」での表示のため「Material 3」の値が利用されています。

https://github.com/flutter/flutter/blob/3f08b617848e26b462560601c4a8b89a93515e77/packages/flutter/lib/src/material/input_decorator.dart#L3043-L3069

contentPaddingの値は「border : true」・「isDense : false」のため、
EdgeInsets.fromLTRB(12, 20, 12, 12)の値のはずです(+32されている)

FontSizeを変えていった場合の縦幅の変化

ほぼデフォルトのままFontSize(8・16・32・64)だけを変更してくと以下のような画面になります。

fontsize1

Widget Inspectorにて確認した値をまとめると以下の通り。

FontSize 縦幅
8 48
16 56
32 80
32 128

SizedBoxで、上記「デフォルト」の縦幅より小さいものにすると、「文字が半分隠れてしまう」という現象が起きる。

例)
FontSize : 32 / 縦幅 : 80 となっていたものを、
SizedBoxでラップして縦幅 : 60とした場合。

fontsize2

「③ FontSize × Line Height(1行の大きさ、単純にフォントが占有する縦幅)」だけの状態

③の縦幅がどんなものであるか(簡単に)検証するために
まずは①②の影響を「0」にすることを考える。

①について
実はこの固定値は「InputDecoration」の設定値である「isDense」を「true」とすることである種、無効化される。

②について
contentPaddingEdgeInsets.symmetric(horizontal: 8),で上書きする(縦幅なし)

上記の設定とした場合、画面と値は以下のようになる。

fontsize3

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」を設定

figmaでの確認

→ これで上記赤枠のテキストボックスの縦幅が最終的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」は必要なことがわかる。

figma2

上下の余白をそれぞれどの程度確保するか決定する
→ 上下に「8」確保する(「48」よりも小さくなるように)

縦幅が「42」となる

結果

fontsize4

ソースコード実装時の細かい工夫

以下のソースコードで「FontSize」や「isDense」・「contentPadding」などを調整した。

今回
FontSize : 18
isDense : true
contentPadding : contentPadding: EdgeInsets.symmetric(horizontal: 8,vertical: 8,)

一覧

https://github.com/beeeyan/flutter_app_sample/blob/64fc34b96fe9be1c1598c1d0424db66286d4240c/lib/feature/field_size/presentation/field_size_sample.dart#L8-L85

TextField部分

https://github.com/beeeyan/flutter_app_sample/blob/64fc34b96fe9be1c1598c1d0424db66286d4240c/lib/feature/field_size/presentation/widget/custom_size_text_field.dart#L3-L98

DropdownButtonFormField部分

https://github.com/beeeyan/flutter_app_sample/blob/64fc34b96fe9be1c1598c1d0424db66286d4240c/lib/feature/field_size/presentation/widget/custom_size_dropdown_button_form_field.dart#L3-L94

DropdownMenu

https://github.com/beeeyan/flutter_app_sample/blob/64fc34b96fe9be1c1598c1d0424db66286d4240c/lib/feature/field_size/presentation/widget/custom_size_dropdown_menu.dart#L3-L90

メモ

  • 全体 : 若干のずれなどがあるのでSizedBoxでも「42」を指定している
  • DropdownButtonではなくDropdownButtonFormFieldとしたのはdecoration部分がTextFieldと同じように扱えたから
  • DropdownMenuのみ、他と「isDense」などの考え方が異なりそうだった。縦幅はinputDecorationThemeの中のconstraintsで最大値・最小値を指定して小さくしている。

Discussion