目次
概要
Flutterを使ってアプリを開発してみようかと思います。今回のブログポストではFlutterでユーザの入力を受ける方法について説明します。
このブログポストで紹介するソースコードは下記のリンクで確認できます。
Flutterプロジェクト生成
Flutterでユーザの入力を貰うためにはTextField
ウィジェットを使います。TextFieldウィジェットを使うためまず、Flutterのプロジェクトを生成します。
flutter create my_app
cd my_app
TextField
プロジェクトを生成したら、main.dart
ファイルを次のように修正してTextField
を表示します。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: Home(),
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextField'),
),
body: Center(
child: Padding(
child: TextField(
decoration: InputDecoration(
labelText: 'Input',
),
),
padding: EdgeInsets.all(20.0),
),
),
);
}
}
上のようにコードをセク生すると次のような画面が確認できます。
TextField
を表示する部分を詳しくみてみましょう。
TextField(
decoration: InputDecoration(
labelText: 'Input',
),
)
上のようにTextFieldを表示することができるし、decoration
パラメーターでInputDecoration
を使って色んな設定ができます。
InputDecoration
InputDecoration
を使うと色んな形のTextField
ウィジェットが使えます。
TextField(
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Enter your email',
labelStyle: TextStyle(color: Colors.redAccent),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(width: 1, color: Colors.redAccent),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(width: 1, color: Colors.redAccent),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
),
keyboardType: TextInputType.emailAddress,
)
このようにInputDecoration
を使うと下記のように色んなデザインを適用することができます。
SingleChildScrollView
TextField
を使うとキーボードが出た時、下記のように特に問題はないです。
しかし、普通デザインのためColumn
ウィジェットとTextField
を使います。
この時、Columnのエリアの上にキーボードが表示されると下記のようなワーニングがでます。
このワーニングを解決するため、使えるものがSingleChildScrollView
ウィジェットです。
SingleChildScrollView
ウィジェットを次のように使うと上の問題を解決することができます。
SingleChildScrollView(
child: Column(
children: [
Container(
width: 300,
height: 300,
margin: EdgeInsets.all(40.0),
color: Colors.lightBlue,
),
Padding(
padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 10.0),
child: TextField(
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Enter your email',
labelStyle: TextStyle(color: Colors.redAccent),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(width: 1, color: Colors.redAccent),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(width: 1, color: Colors.redAccent),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
),
keyboardType: TextInputType.emailAddress,
),
),
],
),
)
SingleChildScrollView
を使うと、TextFieldでキーボードがアクティブされた時、画面がスクロールできる状態になって発生した問題が解決されます。
GestureDetectorとFocusScope
現在キーボードがアクティブになると、キーボードのdone
ボタンを押さないとキーボードは消えません。つまり、TextFieldがFocus
の状態になると、キーボードがアクティブになり、done
キーを押してTextFieldがUnFocus
の状態になるとキーボードが消えます。
普通のアプリのUXはキーボードがアクティブになると、キーボード以外のエリアをタッチすると、キーボードが消えます。このようにキーボード以外のエリアをタッチした時、キーボードが消えるようにするためGestureDetector
ウィジェットとFocusScope
ウィジェットを使います。
それじゃ、キーボード以外のエリアをタッチした時、キーボードを消すため、main.dart
ファイルを次のように修正します。
GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: SingleChildScrollView(...),
),
SingleChildScrollView
ウィジェット中は上で説明したコードなので省略しました。まず、ユーザのイベントを検知するためGestureDetector
を使いました。この時、ユーザが画面をタッチした場合、キーボードからFocus
を消すため、FocusScope
ウィジェットのunfocus
関数を使いました。
このようにGestureDetector
とFocusScope
を使うと、キーボードを消す機能を使えます。
TextFieldの値を使う方法
TextFieldを使う理由はユーザから値を入力して貰って、入力した貰った値を使うためです。それじゃ、TextFieldの値を使う方法について説明します。
onChanged
ユーザがTextFieldに値を入れるとTextFieldウィジェットのonChanged
関数がコールされます。この関数がコールされる時、パラメーターで渡されるtextの値をsetState
を使って保存すれば良いです。
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
String inputText = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextField'),
),
body: Center(
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: SingleChildScrollView(
child: Column(
children: [
Text('$inputText'),
Padding(
padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 10.0),
child: TextField(
onChanged: (text) {
setState(() {
inputText = text;
});
},
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Enter your email',
labelStyle: TextStyle(color: Colors.redAccent),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide:
BorderSide(width: 1, color: Colors.redAccent),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide:
BorderSide(width: 1, color: Colors.redAccent),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
),
keyboardType: TextInputType.emailAddress,
),
),
],
),
),
),
),
);
}
}
値が変更される部分だけもっと詳しく見ると。
...
String inputText = '';
...
Text('$inputText')
...
TextField(
onChanged: (text) {
setState(() {
inputText = text;
});
},
...,
)
...
変更された値を保存するため、StatefulWidget
を生成しました。そして、ユーザの入力を保存するためString変数を定義しました。このように生成したString変数をText
ウィジェットを使って画面へ表示しました。
そしてTextField
ウィジェットのonChanged
関数を使ってユーザが入力した値をsetState
を使って宣言した変数を変更しました。
今から、TextFieldの値を入力すると、次のようにTextFieldの上に入力した内容が出力されることが確認できます。
TextEditingController
上のようにリアルタイムでデータを更新することもできますが、特定なイベントが発生した時、現在入力された値にアクセスしたい時もあります。この時、使うものたTextEditingController
です。
TextEditingController
は次のように使えます。
class _HomeState extends State<Home> {
TextEditingController inputController = TextEditingController();
String inputText = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextField'),
),
body: Center(
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: SingleChildScrollView(
child: Column(
children: [
Text('$inputText'),
Padding(
padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 10.0),
child: TextField(
controller: inputController,
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Enter your email',
labelStyle: TextStyle(color: Colors.redAccent),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide:
BorderSide(width: 1, color: Colors.redAccent),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide:
BorderSide(width: 1, color: Colors.redAccent),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
),
keyboardType: TextInputType.emailAddress,
),
),
ElevatedButton(
onPressed: () {
setState(() {
inputText = inputController.text;
});
},
child: Text('Update'),
),
],
),
),
),
),
);
}
}
そしたら、TextEditingController
を使って入力した値を画面に表示する部分だけみてみましょう。
TextEditingController inputController = TextEditingController();
String inputText = '';
...
Text('$inputText'),
...
TextField(
controller: inputController,
...,
),
...
ElevatedButton(
onPressed: () {
setState(() {
inputText = inputController.text;
});
},
child: Text('Update'),
),
...
まずTextEditingController
を宣言した後、TextField
ウィジェットのcontroller
パラメーターに渡します。そして、ElevatedButton
ボタンが押せた時、setState
を使って変数をアップデートします。この時、inputController.text
のようにTextFieldウィジェットの入力値にアクセスすることができます。
この方法は主にサーバへデータを送るとき使います。
ScaffoldのresizeToAvoidBottomInset
TextField
ウィジェットを使って開発する時、次のようにキーボードとコンテンツの間に空間が表示される場合が発生します。
この時はScaffold
のresizeToAvoidBottomInset
オプションを使うと問題を解決することができます。
Scaffold(
resizeToAvoidBottomInset: false,
appBar: ...,
body: GestureDetector(
onTap: () => Get.focusScope!.unfocus(),
child: SingleChildScrollView(
child: Column(
children: [
...,
],
),
),
),
);
このようにScaffold
のresizeToAvoidBottomInset
オプションをfalse
で設定すると次のように問題が解決されることが確認できます。
完了
これでTextField
ウィジェットを使ってユーザが入力した値にアクセスして使う方法についてみてみました。
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Deku
が開発したアプリを使ってみてください。Deku
が開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。