📝
Flutterでフォーカス制御とキーボード制御を完璧にする方法
Flutterアプリでテキストフィールドを使うとき、キーボードの表示・非表示を適切に制御したいことがありますよね。
今回は、フォーカス制御とキーボード制御について3つの方法を紹介します。
🚨 問題:キーボードが勝手に表示・非表示される
Flutterアプリでテキストフィールドを使うと、以下の問題が発生することがあります。
- テキストフィールドをタップすると自動的にキーボードが表示される
- キーボードを閉じたいのに閉じない
- 複数のテキストフィールドでフォーカスが混乱する
📱 方法1:基本的なフォーカス制御
最もシンプルな方法です。FocusNode
を使ってフォーカスを制御します。
class BasicFocusControl extends StatefulWidget {
const BasicFocusControl({super.key});
State<BasicFocusControl> createState() => _BasicFocusControlState();
}
class _BasicFocusControlState extends State<BasicFocusControl> {
// テキスト入力の制御用
final TextEditingController _controller = TextEditingController();
// フォーカス管理用
final FocusNode _focusNode = FocusNode();
void dispose() {
// リソースを解放
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
// キーボードを閉じるメソッド
void _dismissKeyboard() {
_focusNode.unfocus();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('基本フォーカス制御')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
// フォーカス管理
focusNode: _focusNode,
// テキスト制御
controller: _controller,
// 入力フィールドの装飾
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'テキストを入力',
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _dismissKeyboard,
child: const Text('キーボードを閉じる'),
),
],
),
),
);
}
}
メリット
- 実装が簡単
- 基本的なキーボード制御ができる
- 理解しやすい
デメリット
- 複数フィールドには対応していない
- タップで閉じる機能がない
- 機能が限定的
🔄 方法2:タップでキーボードを閉じる
画面をタップしたときにキーボードを閉じる方法です。
class TapToDismissControl extends StatefulWidget {
const TapToDismissControl({super.key});
State<TapToDismissControl> createState() => _TapToDismissControlState();
}
class _TapToDismissControlState extends State<TapToDismissControl> {
// テキスト入力の制御用
final TextEditingController _controller = TextEditingController();
void dispose() {
// リソースを解放
_controller.dispose();
super.dispose();
}
// キーボードを閉じるメソッド
void _dismissKeyboard() {
// 現在のフォーカスを外してキーボードを閉じる
FocusScope.of(context).unfocus();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('タップでキーボード閉じる')),
body: GestureDetector(
// 画面をタップしたときにキーボードを閉じる
onTap: _dismissKeyboard,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
// テキスト制御
controller: _controller,
// 入力フィールドの装飾
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'テキストを入力',
),
),
const SizedBox(height: 16),
const Text('画面のどこかをタップするとキーボードが閉じます'),
],
),
),
),
);
}
}
メリット
- 直感的な操作
- 実装が簡単
- ユーザビリティが良い
デメリット
- 複数フィールドの制御ができない
- フォーカス移動ができない
- 細かい制御ができない
🎯 方法3:複数フィールドの制御
複数のテキストフィールドを適切に制御する方法です。
実装のポイント
- 複数のFocusNodeを管理
// 各テキストフィールド用のフォーカスノード
final FocusNode _nameFocusNode = FocusNode();
final FocusNode _emailFocusNode = FocusNode();
final FocusNode _messageFocusNode = FocusNode();
- すべてのキーボードを閉じる
void _dismissAllKeyboards() {
_nameFocusNode.unfocus();
_emailFocusNode.unfocus();
_messageFocusNode.unfocus();
}
- 次のフィールドに移動
void _focusNext() {
if (_nameFocusNode.hasFocus) {
_emailFocusNode.requestFocus();
} else if (_emailFocusNode.hasFocus) {
_messageFocusNode.requestFocus();
}
}
メリット
- 複数フィールドを適切に制御
- フォーカス移動ができる
- 細かい制御が可能
デメリット
- 実装が複雑
- コードが長くなる
- 管理が大変
✅ 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 FocusControlComparisonDemo(),
);
}
}
class FocusControlComparisonDemo extends StatefulWidget {
const FocusControlComparisonDemo({super.key});
State<FocusControlComparisonDemo> createState() => _FocusControlComparisonDemoState();
}
class _FocusControlComparisonDemoState extends State<FocusControlComparisonDemo> {
// 方法1用のコントローラーとフォーカスノード
final TextEditingController _basicController = TextEditingController();
final FocusNode _basicFocusNode = FocusNode();
// 方法2用のコントローラー
final TextEditingController _tapController = TextEditingController();
// 方法3用のコントローラーとフォーカスノード
final TextEditingController _nameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _messageController = TextEditingController();
final FocusNode _nameFocusNode = FocusNode();
final FocusNode _emailFocusNode = FocusNode();
final FocusNode _messageFocusNode = FocusNode();
void dispose() {
// すべてのリソースを解放
_basicController.dispose();
_basicFocusNode.dispose();
_tapController.dispose();
_nameController.dispose();
_emailController.dispose();
_messageController.dispose();
_nameFocusNode.dispose();
_emailFocusNode.dispose();
_messageFocusNode.dispose();
super.dispose();
}
// 方法1: 基本的なキーボード制御
void _dismissBasicKeyboard() {
_basicFocusNode.unfocus();
}
// 方法2: タップでキーボードを閉じる
void _dismissTapKeyboard() {
FocusScope.of(context).unfocus();
}
// 方法3: すべてのキーボードを閉じる
void _dismissAllKeyboards() {
_nameFocusNode.unfocus();
_emailFocusNode.unfocus();
_messageFocusNode.unfocus();
}
// 方法3: 次のフィールドに移動
void _focusNext() {
if (_nameFocusNode.hasFocus) {
_emailFocusNode.requestFocus();
} else if (_emailFocusNode.hasFocus) {
_messageFocusNode.requestFocus();
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('フォーカス制御比較'),
actions: [
IconButton(
icon: const Icon(Icons.keyboard_hide),
onPressed: _dismissAllKeyboards,
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 方法1: 基本的なフォーカス制御
_buildSectionTitle('方法1: 基本的なフォーカス制御'),
_buildBasicFocusControl(),
const SizedBox(height: 24),
// 方法2: タップでキーボードを閉じる
_buildSectionTitle('方法2: タップでキーボードを閉じる'),
_buildTapToDismissControl(),
const SizedBox(height: 24),
// 方法3: 複数フィールドの制御
_buildSectionTitle('方法3: 複数フィールドの制御'),
_buildMultiFieldControl(),
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: 基本的なフォーカス制御
Widget _buildBasicFocusControl() {
return Column(
children: [
TextField(
focusNode: _basicFocusNode,
controller: _basicController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'テキストを入力',
),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _dismissBasicKeyboard,
child: const Text('キーボードを閉じる'),
),
],
);
}
// 方法2: タップでキーボードを閉じる
Widget _buildTapToDismissControl() {
return GestureDetector(
onTap: _dismissTapKeyboard,
child: Column(
children: [
TextField(
controller: _tapController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'テキストを入力',
),
),
const SizedBox(height: 8),
const Text(
'画面のどこかをタップするとキーボードが閉じます',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
);
}
// 方法3: 複数フィールドの制御
Widget _buildMultiFieldControl() {
return Column(
children: [
TextField(
focusNode: _nameFocusNode,
controller: _nameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: '名前',
),
onSubmitted: (_) => _focusNext(),
),
const SizedBox(height: 8),
TextField(
focusNode: _emailFocusNode,
controller: _emailController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'メールアドレス',
),
onSubmitted: (_) => _focusNext(),
),
const SizedBox(height: 8),
TextField(
focusNode: _messageFocusNode,
controller: _messageController,
maxLines: 3,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'メッセージ',
),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _dismissAllKeyboards,
child: const Text('すべてのキーボードを閉じる'),
),
],
);
}
// 比較情報を表示するウィジェット
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(タップ制御)を使う場合
- 直感的な操作を重視する場合
- 単一のテキストフィールドの場合
- ユーザビリティを重視する場合
方法3(複数制御)を使う場合
- 複数のテキストフィールドがある場合
- フォーカス移動が必要な場合
- 細かい制御が必要な場合
🧭 おわりに
Flutterでフォーカス制御とキーボード制御を適切に行うことで、ユーザーエクスペリエンスを大幅に向上させることができます。
今回紹介した3つの方法を参考に、自分のアプリに最適なフォーカス制御を実装してみてください!
Discussion