Kodokon kodokon.com

表单:TextField、控制器与校验

用 TextEditingController 驱动你的输入框,并借助 Form 和 TextFormField 一次性校验每一项输入。

9 分钟 · 3 题

在 Kodokon 中打开本课

没有控制器的输入框就是一个黑盒。TextEditingController 让你掌握主动:随时读取 controller.text、预填输入框、在提交后清空它,或用 addListener 监听每一次按键。控制器要在 State 中创建,绝不能放在 build 里 - 否则它会在每次重建时被重新创建。

DART
import 'package:flutter/material.dart';

class NicknameField extends StatefulWidget {
  const NicknameField({super.key});

  @override
  State<NicknameField> createState() =>
      _NicknameFieldState();
}

class _NicknameFieldState extends State<NicknameField> {
  final _controller = TextEditingController();

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

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      decoration: const InputDecoration(
        labelText: 'Nickname',
        border: OutlineInputBorder(),
      ),
      onSubmitted: (value) => debugPrint(value),
    );
  }
}
控制器诞生于 State,消亡于 dispose()。

一旦某个界面有好几个字段需要校验,就采用 FormGlobalKey<FormState>TextFormField 这三件套。每个字段都在 validator 里描述自己的规则:这个函数返回一条错误消息,若一切正常则返回 null。对这个 key 调用一次 validate() 就会同时触发所有校验器。

DART
import 'package:flutter/material.dart';

class SignUpForm extends StatefulWidget {
  const SignUpForm({super.key});

  @override
  State<SignUpForm> createState() => _SignUpFormState();
}

class _SignUpFormState extends State<SignUpForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();

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

  void _submit() {
    if (_formKey.currentState!.validate()) {
      debugPrint('Email: ${_emailController.text}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          TextFormField(
            controller: _emailController,
            keyboardType: TextInputType.emailAddress,
            decoration: const InputDecoration(
              labelText: 'Email',
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Required field';
              }
              if (!value.contains('@')) {
                return 'Invalid address';
              }
              return null;
            },
          ),
          const SizedBox(height: 16),
          FilledButton(
            onPressed: _submit,
            child: const Text('Create account'),
          ),
        ],
      ),
    );
  }
}
validate() 会触发表单中的每一个校验器。

当按钮被点击时,validate() 会运行每一个 validator:返回的消息会以红色显示在对应字段的下方,只要至少有一个字段不合法,该方法就返回 false。因此你的提交逻辑始终守在一个 if 后面 - 想提交非法数据根本不可能。

知识检测

确认你已牢记本课的重点内容。

  1. TextEditingController 是用来做什么的?
    • 用来绘制字段的边框
    • 用来读取和修改字段的文本,并监听它的变化
    • 用来自动校验电子邮件地址
    • 用来关闭键盘
  2. 当值合法时,validator 应该返回什么?
    • true
    • 一个空字符串
    • null
    • 值本身
  3. 为什么要在 State 的 dispose() 方法里调用 _controller.dispose()?
    • 为了清空屏幕上的字段
    • 为了释放资源、避免内存泄漏
    • 为了提交表单