【Flutter】TextFieldの使い方|文字の取得・書き換え

当サイトで紹介する商品・サービス等の外部リンクは、アフィリエイト広告を含む場合があります。
スポンサーリンク
本記事を読むと以下の実行ができます

手で文字を打てて、ボタンでコードから文字を入れたり全部消したりでき、文字数もリアルタイムで表示される入力欄を、TextField と TextEditingController で作れます。

アプリの入力欄は、手で打った文字を読むだけでなく、プログラムから文字を入れたり、中身が変わるたびに何かを表示したりしたい場面がよくあります。

Flutter では、入力欄の部品 TextField に、中身を操作するための TextEditingController を組み合わせると、どちらもできます。

本記事では、サンプル文を入れる、全部消す、文字数を表示する、という3つの動きを持つ入力欄を作りながら、TextField と TextEditingController の使い方を説明します。

本記事は次の人におすすめ
  • Flutter で入力欄(テキストボックス)を作りたい人
  • 入力された文字を、コードから読み取りたい人
  • ボタンなどで、コードから入力欄の文字を書き換えたい人
  • TextEditingController を、なぜ後片付けしなければいけないのか知りたい人
スポンサーリンク

TextField(テキスト入力欄)

TextField は、文字を入力するための入力欄の部品です。最小の書き方は次のとおりです。

const TextField()

見た目や動きは、プロパティで変えます。この記事で使うものは次の3つです。

プロパティ役割
controller入力欄の中身を操作する TextEditingController を渡す
maxLines表示する行数。2以上にすると複数行の入力欄になる
decoration枠線や、何も入力されていないときの案内文(hintText)を決める

参考:TextField(Flutter公式)

TextEditingController(入力欄の中身を操作する)

TextEditingController は、入力欄の中身を読んだり書いたりするための部品です。TextField の controller に渡すと、2つの部品がつながります。

final TextEditingController _controller = TextEditingController();

TextField(controller: _controller)

つなげたあとは、次のことができます。

やりたいこと書き方
いまの文字を読む_controller.text
文字を書き換える_controller.text = ‘新しい文字’
全部消す_controller.clear()
中身が変わったことを知る_controller.addListener(…)、またはウィジェットで監視する

入力された文字を取る方法は、TextEditingController 以外にもあります。それぞれ、呼ばれるタイミングが違います。

方法タイミング
onChanged1文字入力するたびに呼ばれる
onSubmitted入力を終えたとき(キーボードの確定ボタンなど)に呼ばれる
TextEditingController好きなタイミングで text を読める。コードから書き換えもできる

読むだけなら onChanged や onSubmitted でも足ります。この記事のように、コードから文字を入れたり、文字数を出し続けたりするなら、TextEditingController が向いています。

参考:Flutter 公式 API

この記事では、次の動きをする入力欄を作ります。

  • 手で文字を入力できる(複数行)
  • 入力欄の下に、いまの文字数が出る
  • 「サンプルを入れる」ボタンで、コードから文章が入る。カーソルは文字の最後に置く
  • 「全部消す」ボタンで、入力欄が空になる

ソースコード

  • text_input_page.dart

入力欄、文字数の表示、2つのボタンを持つ画面です。

import 'package:flutter/material.dart';

class TextInputPage extends StatefulWidget {
  const TextInputPage({super.key, required this.title});

  final String title;

  @override
  State<TextInputPage> createState() => _TextInputPageState();
}

class _TextInputPageState extends State<TextInputPage> {
  static const _sampleText = 'Hello, nice to meet you.';

  final TextEditingController _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  /// コードから文字を入れ、カーソルを文字の最後に置く。
  void _insertSample() {
    _controller.text = _sampleText;
    _controller.selection = TextSelection.collapsed(offset: _sampleText.length);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              controller: _controller,
              maxLines: 6,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                hintText: '直接入力してください',
              ),
            ),
            const SizedBox(height: 12),
            // 中身が変わるたびに、文字数を作り直す。
            ListenableBuilder(
              listenable: _controller,
              builder: (context, _) => Text('${_controller.text.length} 文字'),
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                FilledButton(
                  onPressed: _insertSample,
                  child: const Text('サンプルを入れる'),
                ),
                const SizedBox(width: 12),
                OutlinedButton(
                  onPressed: _controller.clear,
                  child: const Text('全部消す'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
  • main.dart

動かすための最小の入口です。text_input_page.dart は lib/pages/ に置きます。

import 'package:flutter/material.dart';

import 'pages/text_input_page.dart';

void main() {
  runApp(
    const MaterialApp(home: TextInputPage(title: 'テキスト入力')),
  );
}

解説

画面は StatefulWidget で作っています。TextEditingController は、画面が表示されている間ずっと同じものを使い続けるので、State のフィールドとして1つだけ作ります。

使っている部品は、入力欄の TextField、文字数を出す ListenableBuilder、操作する2つのボタンです。

ListenableBuilder:文字数を更新し続ける

文字数の Text は ListenableBuilder で包んでいます。listenable に TextEditingController を渡すと、中身が変わるたびに builder の中が作り直されます。

TextEditingController は、中身が変わると監視している側に知らせる仕組みを持っています(変化を知らせる部品の仲間として作られています)。ListenableBuilder は、公式の説明では、監視している対象が変わったときに、その部分の画面を作り直す部品で、builder は変化の知らせが来るたびに呼ばれます。ここでは、その知らせを受け取って、文字数の表示だけを更新します。

onChanged の中で setState を呼んでも文字数は出せます。ただ、その場合は画面全体が作り直されます。ListenableBuilder なら、作り直す範囲を文字数の部分だけに絞れます。

_insertSample:コードから文字を入れる

_controller.text = _sampleText; で、入力欄の文字が置き換わります。

ただし、公式の説明では、text を書き換えると、選択範囲や変換中の範囲が消えます。文字を入れたあと、カーソルをどこに置くかも決まらなくなります。

そこで、次の行でカーソルを文字の最後に置いています。

_controller.selection = TextSelection.collapsed(offset: _sampleText.length);

TextSelection.collapsed は、範囲を選ばず、1か所にカーソルを置く指定です。offset に文字数を渡すと、文字の最後になります。

文字とカーソルを同時に決めたいときは、公式の説明のとおり、value に TextEditingValue をまとめて入れる方法もあります。

clear:全部消す

_controller.clear は、入力欄を空にする関数です。ボタンの onPressed に、そのまま渡せます。

dispose:後片付け

TextEditingController は、使い終わったら dispose で後片付けします。公式の説明にも、不要になったら dispose する、と書かれています。画面が閉じるときに呼ばれる State の dispose の中で、_controller.dispose() を呼びます。

エラーと解決策・対策

以下では、公式の説明で注意されている、つまずきやすい点と対処を紹介します。実際に起きたエラーではなく、起こりやすい点をまとめたものです。

文字を入れたのに、カーソルが思った場所にない

_controller.text だけを書き換えた場合、選択範囲が消えるため、カーソルの位置が思った場所にならないことがあります。上のコードのように selection も指定するか、value にまとめて入れてください。

入力のたびに、変な動きや無限ループになる

addListener の中で、同じ TextEditingController の text を書き換えると、書き換えがまた知らせとして届くため、無限に繰り返すおそれがあります。公式の説明でも、この点が注意されています。入力内容を整えたいときは、入力時に文字を変換する TextInputFormatter を使ってください。

まとめ

TextField は入力欄の部品、TextEditingController はその中身を操作する部品です。

  • controller に渡すと、text で読み書きできる
  • text を書き換えると選択範囲が消えるので、カーソルの位置は selection で決める
  • 中身の変化に合わせて表示を変えるなら、ListenableBuilder で作り直す範囲を絞れる
  • 使い終わったら dispose で後片付けする
タイトルとURLをコピーしました