30天计划第10天-表单与输入(TextField)

-
-
2025-11-05 16:49
  • 学习内容

    1. TextField(输入框,支持文本、密码类型)

    2. 表单验证:判断输入是否为空、格式是否正确(如手机号)

  • 实践任务

    1. 做一个 “登录页面”:包含账号输入框、密码输入框(隐藏密码)、登录按钮

    2. 登录按钮点击时验证:账号 / 密码为空则提示 “请填写完整”

一、TextField组件详解

1. 什么是TextField

TextField是Flutter中用于接収用户文本输入的核心组件,支持单行/多行输入、密码隐藏、输入类型限制(如:数字、邮箱)等功能,是表单类页面(登录、注册、设置)的基础。

2. TextField核心属性

属性名作用常用值示例
controller控制输入框内容(获取/设置文本)TextEditingController()
obscureText是否隐藏输入内容(密码框用)true(隐藏)/false(显示,默认)
keyboardType键盘类型(控制弹出的键盘样式)TextInputType.text(默认)、TextInputType.number(数字)、TextInputType.phone(手机号)
decoration输入框装饰(提示文字、边框、图标等)InputDecoration(…)
hintText输入框为空时的提示文字“请输入账号”
labelText输入框的标签文字(可浮动)“账号”
maxLines最大行数(单行输入用1,多行用null)1(默认,单行)
onChanged输入内容变化时的回调(value) => print("输入变化:$value")
enabled是否启用输入框(禁用时为灰色)true(默认)/false(禁用)

3. 基础用法:文本输入框(账号输入)

import 'package:flutter/material.dart';

void main() => runApp(App());

class App extends StatelessWidget{
  App({super.key});

  // 创建控制器,用于获取输入内容
  final TextEditingController _usernameController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('TextField 示例'),),
        body: Padding(
          padding: const EdgeInsets.all(20),
          child: Column(
            children: [
              // 账号输入框
              TextField(
                controller: _usernameController, // 绑定控制器
                keyboardType: TextInputType.text, // 文本键盘
                decoration: InputDecoration(
                  labelText: '账号', // 标签文字
                  hintText: '请输入账号', //  提示文字
                  prefixIcon: const Icon(Icons.person), // 左侧图标
                  border: const OutlineInputBorder(), // 带边框样式
                ),
                // 输入变化时触发
                onChanged: (value) {
                  print('账号输入变化:$value');
                },
              ),
              const SizedBox(height: 20,),
              // 获取输入内容的按钮
              ElevatedButton(
                onPressed: () {
                  // 通过控制器获取输入内容
                  String username = _usernameController.text;
                  print('当前账号:$username');
                },
                child: const Text('获取账号'),
              )
            ],
          ),
        ),
      ),
    );
  }
}
  • 运行效果

4. 密码输入框(隐藏输入内容)

通过obscureText: true实现密码隐藏,配合keyboardType: TextInputType.visiblePassword弹出适合密码输入的键盘。

5. 控制器(TextEditingController)的重要性

  • 获取输入内容:通过controller.text实时获取输入框的文本。
  • 设置默认值:初始化时设置controller.text = “默认值”,输入框会显示预设内容。
  • 清空输入:调用controller.clear()可快速清空输入框。
  • 注意:在StatefulWidget中使用时,需要在dispose方法中释放控制器,避免内存泄露。

    @override
    void dispose() {
      _usernameController.dispose(); // 释放控制器
      _passwordController.dispose();
      super.dispose();
    }

二、表单验证基础

1. 为什么需要表单验证

表单验证用于确保用户输入符合预期格式(如:手机号必须是11位数字、密码长度不少于6位),避免无效数据提交,提升应用健壮性。

2. 表单验证核心组件

  • Form:管理多个输入框的容器,提供整体验证功能。
  • GlobalKey<FormState>:关联Form组件,用于触发验证和获取表单状态。
  • validator:TextField的验证函数,用于返回错误提示文字(null表示验证通过)。

3. 基本验证流程

  • 创建GlobalKey<FormState>用于关联表单
  • 用Form组件包裹所有TextField,并绑定key。
  • 为每个TextField添加validator函数,定义验证规则。
  • 点击按钮时,通过key.currentState!.validate()触发所有输入框的验证。
  • 验证通过(返回true)则处理数据;否则显示错误提示。

4. 代码示例:简单表单验证

import 'package:flutter/material.dart';

void main() => runApp(App());

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

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  // 1. 创建表单key
  final _formKey = GlobalKey<FormState>();

  // 2. 创建控制器
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();

  // 3. 登录按钮点击事件
  void _submitForm() {
    // 触发所有输入框的验证
    if (_formKey.currentState!.validate()) {
      // 验证通过:获取输入内容并处理
      String username = _usernameController.text;
      String password = _passwordController.text;
      print('登录信息:账号=$username,密码=$password。');
    }
  }

  @override void dispose() {
    _usernameController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('表单验证示例'),),
        body: Padding(
          padding: const EdgeInsets.all(20),
          // 4. 用Form包裹输入框,绑定key
          child: Form(
            key: _formKey,
            child: Column(
              children: [
                // 账号输入框
                TextFormField(  // Form中通常用TextFormField而不是TextField
                  controller: _usernameController,
                  decoration: InputDecoration(
                    labelText: '账号',
                    hintText: '请输入账号',
                    prefixIcon: const Icon(Icons.person),
                    border: const OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return '请输入账号'; // 验证失败,返回错误提示
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 20,),
                // 密码输入框
                TextFormField(
                  controller: _passwordController,
                  obscureText: true,
                  decoration: InputDecoration(
                    labelText: '密码',
                    hintText: '请输入密码(不少于6位)',
                    prefixIcon: const Icon(Icons.lock),
                    border: const OutlineInputBorder()
                  ),
                  // 密码验证规则:不能为空且长度不少于6位
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return '密码不能为空';
                    }
                    if (value.length < 6) {
                      return '密码长度不能少于6位';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 30,),
                // 提交按钮
                ElevatedButton(
                  onPressed: _submitForm,
                  style: ElevatedButton.styleFrom(
                    minimumSize: const Size(double.infinity, 50),
                  ),
                  child: const Text('登录', style: TextStyle(fontSize: 18),),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}
  • 运行效果

5. 说明

  • TextFormField VS TextField:在Form中推荐使用TextFormField,它继承自TextField并增加了validator等表单相关属性。
  • validator函数:参数value是输入框的内容(可空),返回null表示验证通过,返回字符串则显示该字符串作为错误提示。
  • 错误提示样式:默认显示为红色小字,位于输入框下方,可通过InputDecoration.errorStyle自定义。

三、综合实战

1. 任务需求:

  • 开发一个登录页面,包含:
    • 账号输入框(TextFormField):带标签、提示文字、用户图标
    • 密码输入框(TextFormField):带标签、提示文字、锁图标,隐藏输入内容
    • 登录按钮(ElevatedButton):占满宽度,点击触发验证
  • 验证逻辑
    • 账号为空 → 提示“请输入账号”
    • 密码为空 → 提示“请输入密码”
    • 两者都为空 → 同时提示两个错误
    • 验证通过 → 弹出对话框提示“登录成功”

2. 代码实现

  • 登录页代码(login_page.dart)
import 'package:flutter/material.dart';

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

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  // 1. 创建表单key
  final _formKey = GlobalKey<FormState>();

  // 2. 创建控制器
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();

  // 3. 登录按钮点击事件
  void _submitForm() {
    // 触发所有输入框的验证
    if (_formKey.currentState!.validate()) {
      // 验证通过:获取输入内容并处理
      String username = _usernameController.text;
      String password = _passwordController.text;
      
      // 显示登录成功对话框
      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('登录成功'),
          content: Text('账号:$username\n密码:$password'),
          actions: [
            TextButton(onPressed: () => Navigator.pop(context), child: const Text('确定'))
          ],
        ),
      );

    }
  }

  @override void dispose() {
    _usernameController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 页面背景色
      backgroundColor: Colors.grey[50],
      body: Center(
        child: SingleChildScrollView(
          // 防止键盘弹出时内容溢出
          padding: const EdgeInsets.symmetric(horizontal: 20),
          child: Form(
            key: _formKey,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // 应用图标
                const Icon(
                  Icons.lock,
                  size: 80,
                  color: Colors.blue,
                ),
                const SizedBox(height: 40,),

                // 账号输入框
                TextFormField(
                  controller: _usernameController,
                  keyboardType: TextInputType.text,
                  // 自动获取焦点(进入页面后自动弹出键盘)
                  autofocus: true,
                  decoration: InputDecoration(
                    labelText: '账号',
                    hintText: '请输入手机号',
                    prefixIcon: const Icon(Icons.person),
                    border: const OutlineInputBorder(
                      borderRadius: BorderRadius.all(Radius.circular(10)),
                    ),
                    // 聚焦时边框颜色
                    focusedBorder: OutlineInputBorder(
                      borderSide: const BorderSide(color: Colors.blue, width: 2),
                      borderRadius: BorderRadius.circular(10),
                    )
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return '请输入账号'; // 验证失败,返回错误提示
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 20,),
                // 密码输入框
                TextFormField(
                  controller: _passwordController,
                  obscureText: true,
                  keyboardType: TextInputType.visiblePassword,
                  decoration: InputDecoration(
                    labelText: '密码',
                    hintText: '请输入密码(不少于6位)',
                    prefixIcon: const Icon(Icons.lock),
                    border: const OutlineInputBorder(
                      borderRadius: BorderRadius.all(Radius.circular(10))
                    ),
                    focusedBorder: OutlineInputBorder(
                      borderSide: const BorderSide(color: Colors.blue, width: 2),
                      borderRadius: BorderRadius.circular(10)
                    )
                  ),
                  // 密码验证规则:不能为空且长度不少于6位
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return '密码不能为空';
                    }
                    if (value.length < 6) {
                      return '密码长度不能少于6位';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 30,),
                // 提交按钮
                ElevatedButton(
                  onPressed: _submitForm,
                  style: ElevatedButton.styleFrom(
                    minimumSize: const Size(double.infinity, 50),
                    backgroundColor: Colors.blue,
                    foregroundColor: Colors.white,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(10), // 圆角
                    )
                  ),
                  child: const Text('登录', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),),
                )
              ],
            ),
          ),
        
        ),
       ),
    );
  }
}
  • 入口文件(main.dart)

import 'pages/login_page.dart';
import 'package:flutter/material.dart';

void main() => runApp(App());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '登录页面示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const LoginPage(),
    );
  }
}
  • 运行效果


目录