[Flutter] Textfieldウィジェット

2023-03-18 hit count image

今回のブログポストではFlutterでユーザの入力をもらえるTextfieldウィジェットを使う方法について説明します。

概要

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),
        ),
      ),
    );
  }
}

上のようにコードをセク生すると次のような画面が確認できます。

Flutter - textfield

Textfieldを表示する部分を詳しくみてみましょう。

TextField(
  decoration: InputDecoration(
    labelText: 'Input',
  ),
)

上のようにTextfieldを表示することができるし、decrationパラメーターで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を使うと下記のように色んなデザインを適用することができます。

Flutter - Textfield InputDecoration

SingleChildScrollView

Textfieldを使うとキーボードが出た時、下記のように特に問題はないです。

Flutter - Textfield keyboard

しかし、普通デザインのためColumnウィジェットとTextfieldを使います。

Flutter - Textfield column

この時、Columnのエリアの上にキーボードが表示されると下記のようなワーニングがでます。

Flutter - 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でキーボードがアクティブされた時、画面がスクロールできる状態になって発生した問題が解決されます。

Flutter - Textfield SingleChildScrollView

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関数を使いました。

このようにGestureDetectorFocusScopeを使うと、キーボードを消す機能を使えます。

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の値を入力すると、次のようにTextfeildの上に入力した内容が出力されることが確認できます。

Flutter - Textfield SingleChildScrollView

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ウィジェットを使って開発する時、次のようにキーボードとコンテンツの間に空間が表示される場合が発生します。

Flutter - Textfield resizeToAvoidBottomInset error

この時はScaffoldresizeToAvoidBottomInsetオプションを使うと問題を解決することができます。

Scaffold(
  resizeToAvoidBottomInset: false,
  appBar: ...,
  body: GestureDetector(
    onTap: () => Get.focusScope!.unfocus(),
    child: SingleChildScrollView(
      child: Column(
        children: [
          ...,
        ],
      ),
    ),
  ),
);

このようにScaffoldresizeToAvoidBottomInsetオプションをfalseで設定すると次のように問題が解決されることが確認できます。

Flutter - Textfield resizeToAvoidBottomInset

完了

これでTextfieldウィジェットを使ってユーザが入力した値にアクセスして使う方法についてみてみました。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。

Posts