🐥
FlutterでTextFieldのサイズを動的に調整する方法
Flutterアプリでテキストフィールドを使うとき、固定サイズだと使いにくいことがありますよね。
今回は、テキストフィールドのサイズ調整について3つの方法を紹介します。
🚨 問題:テキストフィールドにはサイズ調整機能がない
FlutterのTextField
ウィジェットには、直接的なサイズ調整機能がありません。
-
TextField
自体にはheight
やwidth
プロパティがない - デフォルトでは内容に応じて自動調整される
- 固定サイズにしたい場合は別の方法が必要
📏 方法1:Containerで固定サイズを指定
最もシンプルな方法です。Container
でTextField
を囲んで固定サイズを指定します。
Container(
// 固定の高さを指定(ピクセル単位)
height: 128,
// ボーダーの装飾を設定
decoration: BoxDecoration(
// ボーダーの色を指定
border: Border.all(
color: Colors.grey.shade300,
width: 0.4,
),
// 角丸を指定
borderRadius: BorderRadius.circular(4),
),
// Containerの中身としてTextFieldを配置
child: TextField(
// 最大行数をnullにすることで無制限に設定
maxLines: null,
// 親コンテナいっぱいに拡張
expands: true,
// テキストを上揃えに配置
textAlignVertical: TextAlignVertical.top,
// 入力フィールドの装飾
decoration: const InputDecoration(
// デフォルトのボーダーを非表示
border: InputBorder.none,
// 内側の余白を設定
contentPadding: EdgeInsets.all(4),
// プレースホルダーテキスト
hintText: 'ここにテキストを入力してください...',
),
// テキストのスタイル
style: const TextStyle(fontSize: 14),
),
)
メリット
- 実装が簡単
- 確実に固定サイズになる
- レイアウトが安定する
デメリット
- テキストが長いと見切れる
- 短いテキストでも大きなスペースを取る
- ユーザビリティが悪い
🔄 方法2:テキスト量に応じて動的調整
テキストの量に応じて自動的にサイズを調整する方法です。
実装のポイント
- TextPainterでテキストサイズを計算
// テキストの描画に必要なサイズを計算するためのツール
final textPainter = TextPainter(
// 計算したいテキストとスタイルを指定
text: TextSpan(text: text, style: TextStyle(fontSize: 14)),
// テキストの方向を指定(左から右)
textDirection: TextDirection.ltr,
// 行数制限なし(改行で自動的に複数行になる)
maxLines: null,
);
// 利用可能な幅を指定してレイアウトを計算
// 32は左右のパディング分を引いた幅
textPainter.layout(maxWidth: MediaQuery.of(context).size.width - 32);
// 計算された高さを取得
final height = textPainter.height;
- AnimatedContainerでスムーズな変更
// アニメーション付きのコンテナ
AnimatedContainer(
// アニメーションの時間を200ミリ秒に設定
duration: const Duration(milliseconds: 200),
// 計算された高さを適用
height: calculatedHeight,
// 中身としてTextFieldを配置
child: TextField(...),
)
- 最小・最大サイズの制限
// 計算された高さに余白を追加し、最小・最大値の範囲内に制限
// clamp(最小値, 最大値)で範囲を制限し、toDouble()でdouble型に変換
final newHeight = (textPainter.height + 20).clamp(80, 200).toDouble();
メリット
- テキストの量に応じて最適なサイズになる
- ユーザビリティが良い
- スクロールが不要
デメリット
- 実装が複雑
- レイアウトが頻繁に変わる
- パフォーマンスへの影響がある
✅ 3つの方法を比較した完全版コード
DartPadでそのまま動かせる、3つの方法を比較できるコードです。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'テキストフィールドサイズ調整デモ',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TextFieldComparisonDemo(),
);
}
}
class TextFieldComparisonDemo extends StatefulWidget {
const TextFieldComparisonDemo({super.key});
State<TextFieldComparisonDemo> createState() => _TextFieldComparisonDemoState();
}
class _TextFieldComparisonDemoState extends State<TextFieldComparisonDemo> {
// 動的テキストフィールドの高さ
double _dynamicHeight = 80;
// 動的テキストフィールド用のコントローラー
final TextEditingController _dynamicController = TextEditingController();
void initState() {
super.initState();
// 動的テキストフィールドの高さ更新リスナーを追加
_dynamicController.addListener(_updateDynamicHeight);
}
void dispose() {
// リスナーを削除
_dynamicController.removeListener(_updateDynamicHeight);
// コントローラーを破棄
_dynamicController.dispose();
super.dispose();
}
// 動的テキストフィールドの高さを更新するメソッド
void _updateDynamicHeight() {
final text = _dynamicController.text;
final textSpan = TextSpan(
text: text.isEmpty ? 'A' : text,
style: const TextStyle(fontSize: 14),
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
maxLines: null,
);
// 画面幅から余白を引いた幅で計算
textPainter.layout(maxWidth: MediaQuery.of(context).size.width - 64);
// 最小80px、最大200pxの範囲で高さを制限
final newHeight = (textPainter.height + 20).clamp(80, 200).toDouble();
if (newHeight != _dynamicHeight) {
setState(() {
_dynamicHeight = newHeight;
});
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('テキストフィールドサイズ比較'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 方法1: Containerで固定サイズ
_buildSectionTitle('方法1: Containerで固定サイズ'),
_buildFixedSizeTextField(),
const SizedBox(height: 24),
// 方法2: 動的サイズ調整
_buildSectionTitle('方法2: テキスト量に応じて動的調整'),
_buildDynamicTextField(),
Text('現在の高さ: ${_dynamicHeight.toStringAsFixed(1)}px'),
const SizedBox(height: 24),
// 方法3: デフォルトのTextField
_buildSectionTitle('方法3: デフォルトのTextField'),
_buildDefaultTextField(),
const SizedBox(height: 24),
// 比較説明
_buildComparisonInfo(),
],
),
),
);
}
// セクションタイトルを表示するウィジェット
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
);
}
// 方法1: Containerで固定サイズのテキストフィールド
Widget _buildFixedSizeTextField() {
return Container(
// 固定の高さを128pxに設定
height: 128,
// ボーダーの装飾
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey.shade300,
width: 0.4,
),
borderRadius: BorderRadius.circular(4),
),
child: TextField(
// 複数行入力可能
maxLines: null,
// 親コンテナいっぱいに拡張
expands: true,
// テキストを上揃え
textAlignVertical: TextAlignVertical.top,
// 入力フィールドの装飾
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.all(4),
hintText: '固定サイズ(128px)のテキストフィールド',
),
style: const TextStyle(fontSize: 14),
),
);
}
// 方法2: 動的サイズ調整のテキストフィールド
Widget _buildDynamicTextField() {
return AnimatedContainer(
// アニメーション時間を200msに設定
duration: const Duration(milliseconds: 200),
// 計算された高さを適用
height: _dynamicHeight,
// ボーダーの装飾
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey.shade300,
width: 0.4,
),
borderRadius: BorderRadius.circular(4),
),
child: TextField(
controller: _dynamicController,
maxLines: null,
expands: true,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.all(4),
hintText: '動的サイズ調整(80-200px)のテキストフィールド',
),
style: const TextStyle(fontSize: 14),
),
);
}
// 方法3: デフォルトのTextField
Widget _buildDefaultTextField() {
return Container(
// ボーダーの装飾
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey.shade300,
width: 0.4,
),
borderRadius: BorderRadius.circular(4),
),
child: TextField(
// デフォルトでは単一行
// maxLines: null を設定すると複数行になる
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.all(4),
hintText: 'デフォルトのテキストフィールド(単一行)',
),
style: const TextStyle(fontSize: 14),
),
);
}
// 比較情報を表示するウィジェット
Widget _buildComparisonInfo() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'📊 比較表',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text('• 方法1: 固定サイズ - 実装簡単、レイアウト安定'),
Text('• 方法2: 動的調整 - ユーザビリティ良好、実装複雑'),
Text('• 方法3: デフォルト - シンプル、機能制限あり'),
],
),
);
}
}
🎯 使い分けのポイント
方法1(固定サイズ)を使う場合
- レイアウトの安定性が重要な場合
- 実装を簡単に済ませたい場合
- テキストの長さが予測可能な場合
方法2(動的調整)を使う場合
- ユーザビリティを重視する場合
- テキストの長さが予測できない場合
- モダンなUIを目指す場合
方法3(デフォルト)を使う場合
- 単純な入力フィールドが必要な場合
- 複数行入力が不要な場合
- 最小限の実装で済ませたい場合
🧭 おわりに
Flutterでテキストフィールドを使う際は、用途に応じて適切なサイズ調整方法を選択することが重要です。
今回紹介した3つの方法を参考に、自分のアプリに最適なテキストフィールドを実装してみてください!
Discussion