Flutter 系列教程:常用基础组件 (下) - `TextField` 和 `Form`

在大多数应用中,我们不仅需要向用户展示信息,还需要获取用户的输入。Flutter 提供了强大的 TextField 组件来处理文本输入,并通过 Form 组件来对多个输入进行分组、验证和管理。

学习目标

  • 掌握 TextField 的基本用法,并学会使用 TextEditingController 来获取和控制输入内容。
  • 学会使用 InputDecoration 来美化输入框,添加标签、提示、图标和边框。
  • 理解 Form Widget 的作用,并学会使用 GlobalKey 来管理表单状态。
  • 掌握 TextFormField,学会如何编写 validator 函数来进行输入验证。

1. TextField:获取单行输入

TextField 是一个允许用户通过硬件键盘或屏幕键盘输入文本的组件。要有效地使用它,我们必须学会如何获取监听其内容。

核心:TextEditingController

获取 TextField 内容最常用和推荐的方式是使用 TextEditingController。它是一个可监听的对象,你可以用它来:

  • 读取 TextField 中的当前文本 (controller.text)。
  • 在代码中设置 TextField 的文本 (controller.text = 'new value')。
  • 监听文本的变化 (controller.addListener(...))。

重要TextEditingController 会持有状态,因此它必须在 StatefulWidgetState 对象中被创建和管理。并且,为了避免内存泄漏,dispose 方法中销毁它是一个好习惯。

美化输入框:decoration

TextFielddecoration 属性接收一个 InputDecoration 对象,它能讓你定制输入框的几乎所有视觉方面。

  • 常用属性
    • labelText: 当输入框为空且未聚焦时显示的浮动标签。
    • hintText: 当输入框为空时显示的灰色提示文字。
    • helperText: 在输入框下方显示的小段帮助文字。
    • errorText: 在输入框下方显示的红色错误信息(通常用于表单验证)。
    • icon, prefixIcon, suffixIcon: 在输入框外部、内部前方、内部后方添加图标。
    • border: 定义输入框的边框样式,常用 OutlineInputBorder() (四周边框) 或 UnderlineInputBorder() (下划线边框)。

代码示例:一个简单的登录输入框

java 复制代码
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(
      home: Scaffold(
        appBar: Text('TextField Demo'),
        body: Padding(
          padding: EdgeInsets.all(20.0),
          child: SimpleInputForm(),
        ),
      ),
    );
  }
}

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

  @override
  State<SimpleInputForm> createState() => _SimpleInputFormState();
}

class _SimpleInputFormState extends State<SimpleInputForm> {
  // 1. 创建 TextEditingController
  late final TextEditingController _usernameController;
  String _displayText = '...';

  @override
  void initState() {
    super.initState();
    _usernameController = TextEditingController();
  }

  @override
  void dispose() {
    // 3. 在 widget 销毁时,dispose controller
    _usernameController.dispose();
    super.dispose();
  }

  void _showUsername() {
    setState(() {
       // 4. 通过 controller.text 获取值
      _displayText = _usernameController.text;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextField(
          controller: _usernameController, // 2. 绑定 controller
          decoration: InputDecoration(
            labelText: '用户名',
            hintText: '请输入您的用户名',
            prefixIcon: const Icon(Icons.person),
            border: const OutlineInputBorder(),
          ),
        ),
        const SizedBox(height: 20),
         TextField(
          obscureText: true, // 设置为密码输入框
          decoration: InputDecoration(
            labelText: '密码',
            prefixIcon: const Icon(Icons.lock),
            border: const OutlineInputBorder(),
          ),
        ),
        const SizedBox(height: 30),
        ElevatedButton(
          onPressed: _showUsername,
          child: const Text('获取用户名'),
        ),
        const SizedBox(height: 20),
        Text('您输入了: $_displayText'),
      ],
    );
  }
}

2. FormTextFormField:处理复杂表单

当你有多个输入框,并且需要对它们进行统一的验证 (Validation)提交 (Submission) 时,就应该使用 Form Widget。

  • Form : 作为一个容器,它可以追踪其内部所有 FormField 的状态。
  • TextFormField : TextField 的一个特殊版本,它天生就能与 Form 协同工作。它比 TextField 增加了一个关键属性:validator
  • GlobalKey<FormState> : 这是操作 Form 的钥匙。通过这个 key,我们可以调用 Form 的方法,如 validate()save()

验证流程

  1. 创建一个 GlobalKey<FormState> 并将其赋给 Form Widget的 key 属性。
  2. TextFormField 中提供一个 validator 函数。这个函数接收当前输入框的值,如果值有效,返回 null ;如果无效,返回一个描述错误的字符串
  3. 当用户尝试提交时(例如点击"登录"按钮),调用 _formKey.currentState!.validate()
  4. 这会触发 Form 内部所有 TextFormFieldvalidator 函数。
    • 如果所有 validator 都返回 nullvalidate() 方法返回 true
    • 只要有一个 validator 返回了错误字符串,validate() 方法就会返回 false,并且该错误字符串会自动显示在对应的 TextFormField下方。

代码示例:一个带验证的登录表单

这个例子展示了如何使用 FormTextFormFieldGlobalKey 来构建一个带有输入验证功能的完整登录表单。

php 复制代码
import 'package.flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Form Validation Demo')),
        body: const Padding(
          padding: EdgeInsets.all(24.0),
          child: LoginForm(),
        ),
      ),
    );
  }
}

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

  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  // 1. 创建 GlobalKey
  final _formKey = GlobalKey<FormState>();

  void _submitForm() {
    // 3. 调用 key 的 validate 方法
    if (_formKey.currentState!.validate()) {
      // 如果验证通过,则显示一个 Snackbar
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('处理数据...')),
      );
      // 在这里可以执行登录逻辑,比如调用 API
    }
  }

  @override
  Widget build(BuildContext context) {
    // 2. 用 Form Widget 包裹你的表单字段
    return Form(
      key: _formKey,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextFormField(
            decoration: const InputDecoration(
              labelText: '邮箱',
              hintText: '请输入您的邮箱地址',
              border: OutlineInputBorder(),
            ),
            keyboardType: TextInputType.emailAddress,
            // 验证逻辑
            validator: (value) {
              if (value == null || value.isEmpty) {
                return '邮箱不能为空';
              }
              if (!value.contains('@')) {
                return '请输入有效的邮箱地址';
              }
              return null; // 返回 null 表示验证通过
            },
          ),
          const SizedBox(height: 20),
          TextFormField(
            decoration: const InputDecoration(
              labelText: '密码',
              hintText: '请输入您的密码',
              border: OutlineInputBorder(),
            ),
            obscureText: true,
            // 验证逻辑
            validator: (value) {
              if (value == null || value.isEmpty) {
                return '密码不能为空';
              }
              if (value.length < 6) {
                return '密码长度不能少于6位';
              }
              return null; // 返回 null 表示验证通过
            },
          ),
          const SizedBox(height: 30),
          ElevatedButton(
            onPressed: _submitForm,
            style: ElevatedButton.styleFrom(
              minimumSize: const Size.fromHeight(50), // 设置按钮高度
            ),
            child: const Text('登录', style: TextStyle(fontSize: 18)),
          ),
        ],
      ),
    );
  }
}

总结

至此,我们已经掌握了 Flutter 中处理用户输入的两大核心利器。

  • 对于简单的、单个的输入 ,使用 TextField 配合 TextEditingController 就足够了。
  • 对于需要多个输入字段、统一验证和管理的场景 (如登录、注册),则必须使用 FormTextFormFieldGlobalKey<FormState> ` 的黄金组合。

现在,你已经具备了构建具有完整信息展示和用户交互功能页面的核心能力。在接下来的教程中,我们将学习如何展示列表和网格数据,这是构建信息流、商品列表等页面的关键。我们下篇见!

相关推荐
小奋斗4 小时前
浏览器原理之详解渲染进程
前端·面试
伶俜monster4 小时前
搞定 Monorepo,工程能力升级,升职加薪快人一步
前端·架构
刘发财4 小时前
一行代码将html页面转成矢量PDF
前端·javascript·vue.js
Dorian_Ov04 小时前
GeoServer添加要素图层的SLD样式文件以及中文乱码相关解决方案
前端·gis
Holin_浩霖4 小时前
前端开发者的 Web3 全图解实战 一
前端
uhakadotcom4 小时前
deno在2025年新出了哪些api可供使用?
前端·面试·github
uhakadotcom4 小时前
2025年honojs提供了哪些新的基础能力与API可以使用?
前端·javascript·面试
Pluto5384 小时前
第一个成功在APP store 上架的APP
前端·后端·cursor
SWAYING4 小时前
每天不到1毛钱,用AI自动追踪GitHub热门项目并生成营销文案 | Cursor全程开发
前端·后端