回應文字框內容的更改

在某些情境中,我們可能需要在每次文字框的文字內容變化時都呼叫回呼函式。例如,當建立一個有自動填充功能的搜尋頁面時,我們希望根據使用者輸入的內容來更新回傳的結果。

那麼如何每次在文字內容改變時調用回呼函式呢?在Flutter中,我們提供了兩種選擇:

  1. TextFieldTextFormField 繫結 onChanged() 回呼

  2. 使用 TextEditingController

1. 給 TextFieldTextFormField 繫結 onChanged() 回呼

最簡單的方法是給 TextField 繫結 onChanged() 回呼。每當文字內容改變時,回呼函式會被觸發。

在下面的範例中,每次 text 的值改變,會在控制台中列印出當前文字框的值。

在處理使用者的輸入內容時,很重要的一點是要使用 characters,因為使用者的輸入內容可能會包含複雜的字元。 characters 會保證每一個顯示到使用者字元都被計數到。

TextField(
  onChanged: (text) {
    print('First text field: $text (${text.characters.length})');
  },
),

2. 使用 TextEditingController

另外一種更強大但是更復雜的方法是繫結 TextEditingController 作為 TextFieldTextFormFieldcontroller 屬性。

你可以透過如下步驟,使用 addListener() 方法來監聽控制,實現在文字更改時收到通知:

  1. 建立一個 TextEditingController

  2. TextEditingController 繫結到 text field

  3. 建立一個函式來列印最新值

  4. 監聽控制器的變化

建立一個 TextEditingController

建立一個 TextEditingController

// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}

// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a text controller. Later, use it to retrieve the
  // current value of the TextField.
  final myController = TextEditingController();

  @override
  void dispose() {
    // Clean up the controller when the widget is removed from the
    // widget tree.
    myController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Fill this out in the next step.
  }
}

給 text field 繫結 TextEditingController

TextEditingController 必須繫結到 TextField 或者是 TextFormField 才能被正常的使用。一旦繫結,就能夠開始監聽文字框的變化。

TextField(
  controller: myController,
),

建立一個列印當前值的方法

現在,我們需要一個每當表單項變化都會執行的函式。在下面的範例中,我們會在 _MyCustomFormState 類別中建立一個方法,實現列印出文本框當前值。

void _printLatestValue() {
  final text = myController.text;
  print('Second text field: $text (${text.characters.length})');
}

監聽控制器的變化

最後,需要監聽 TextEditingController 並且在 text 值變化時運行 _printLatestValue() 方法。我們需要使用 addListener() 方法來實現這個功能。

下面的範例會在類 _MyCustomFormState 初始化的時候開始監聽變化,dispose 時停止監聽。

@override
void initState() {
  super.initState();

  // Start listening to changes.
  myController.addListener(_printLatestValue);
}
@override
void dispose() {
  // Clean up the controller when the widget is removed from the widget tree.
  // This also removes the _printLatestValue listener.
  myController.dispose();
  super.dispose();
}

互動式範例

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Retrieve Text Input',
      home: MyCustomForm(),
    );
  }
}

// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}

// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a text controller and use it to retrieve the current value
  // of the TextField.
  final myController = TextEditingController();

  @override
  void initState() {
    super.initState();

    // Start listening to changes.
    myController.addListener(_printLatestValue);
  }

  @override
  void dispose() {
    // Clean up the controller when the widget is removed from the widget tree.
    // This also removes the _printLatestValue listener.
    myController.dispose();
    super.dispose();
  }

  void _printLatestValue() {
    final text = myController.text;
    print('Second text field: $text (${text.characters.length})');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Retrieve Text Input'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              onChanged: (text) {
                print('First text field: $text (${text.characters.length})');
              },
            ),
            TextField(
              controller: myController,
            ),
          ],
        ),
      ),
    );
  }
}