Flutter那些事-交互式组件

0 阅读4分钟

TextField

在 Flutter 开发中,TextField 是用于接收用户文本输入的核心组件,它功能强大且高度可定制。本文将全面介绍其核心概念、常用属性、事件监听、焦点管理以及自定义样式,帮助你快速掌握并灵活运用。


1. 核心概念与基本使用

TextField 遵循 Material Design 设计规范。最简单的用法是直接创建一个 TextField 实例,你会在屏幕上看到一个带下划线的输入框。

TextField(
  decoration: InputDecoration(
    hintText: '请输入内容', // 提示文字
  ),
)

如果要完全移除装饰(包括下划线和为标签预留的空间),可以将 decoration 属性设置为 null 。

TextField(
  decoration:none,
)

Flutter 还提供了 TextFormField,它是对 TextField 的进一步封装,专门用于与 Form 表单集成,可以方便地实现表单验证和与其他表单字段的交互功能 -6

2. 常用属性详解

TextField 提供了丰富的属性来控制其外观和行为。

2.1 控制器(controller

controller 是 TextEditingController 类型的对象,它是与文本框交互的句柄。通过控制器可以获取或设置文本框的内容、监听文本变化、控制选中区域等 -2。在实际开发中,如果你需要监听文本变化或动态修改文本框的值,建议显式地创建一个 TextEditingController 并管理其生命周期(在 StatefulWidget 的 dispose 方法中释放)-1

final TextEditingController _controller = TextEditingController();

@override
void initState() {
  super.initState();
  _controller.text = '初始值'; // 设置初始值
  // 注意:设置完值后,可以通过调整 selection 属性来改变光标位置 [citation:2][citation:7]
  _controller.selection = TextSelection.fromPosition(TextPosition(offset: _controller.text.length));
}

@override
Widget build(BuildContext context) {
  return TextField(
    controller: _controller,
  );
}

@override
void dispose() {
  _controller.dispose(); // 不要忘记释放
  super.dispose();
}

2.2 装饰(decoration

decoration 属性接受一个 InputDecoration 对象,用于控制输入框的外观,如标签、提示文本、图标、边框等。

属性描述示例
labelText输入框上方的标签文本。InputDecoration(labelText: '用户名')
hintText输入框内的提示文本,用户输入后消失。InputDecoration(hintText: '请输入用户名')
prefixIcon显示在输入框前部的图标。InputDecoration(prefixIcon: Icon(Icons.person))
suffixIcon显示在输入框后部的图标,常用于密码可见性切换。InputDecoration(suffixIcon: Icon(Icons.visibility))
border输入框的边框样式。InputDecoration(border: OutlineInputBorder())
filled / fillColor是否填充背景色及填充颜色。InputDecoration(filled: true, fillColor: Colors.grey[200])

2.3 键盘与输入类型

  • keyboardType: 设置弹出键盘的类型,如 TextInputType.text(文本)、TextInputType.number(数字)、TextInputType.emailAddress(邮箱地址,会显示“@”和“.”)、TextInputType.phone(电话号码键盘)等 -2
  • textInputAction: 设置键盘右下角动作按钮的图标,如 TextInputAction.done(完成)、TextInputAction.search(搜索)、TextInputAction.next(下一项)等,方便用户进行表单导航 -2
  • obscureText: 设置为 true 时,输入内容会被隐藏,用于密码输入框 -2

2.4 输入格式与限制

  • maxLines: 设置输入框的最大行数。默认为 1,设置为 null 则表示无限多行 -2
  • minLines: 设置输入框的最小行数,需与 maxLines 配合使用 -8
  • maxLength: 设置允许输入的最大字符数。设置后,输入框右下角会显示字符计数器 -2
  • inputFormatters: 接受一个 TextInputFormatter 列表,用于限制输入格式,例如只允许输入数字或指定格式 -2
TextField(
  inputFormatters: [
    FilteringTextInputFormatter.digitsOnly, // 只允许输入数字
  ],
)
  • enabled: 设置为 false 时,输入框将被禁用,不接收输入且显示禁用态样式 -2

2.5 光标样式

可以通过 cursorWidthcursorHeightcursorRadius 和 cursorColor 属性来自定义光标的宽度、高度、圆角和颜色 -2

TextField(
  cursorColor: Colors.red, // 光标颜色
  cursorWidth: 3.0,       // 光标宽度
  cursorRadius: Radius.circular(5.0), // 光标圆角
)

3. 事件监听与焦点管理

3.1 监听文本变化

有两种方式监听文本变化 -2

  1. onChanged 回调:当用户每次改变输入框中的文本时都会触发,直接返回当前文本值。
  2. TextEditingController 监听器:通过 controller.addListener(() {...}) 添加监听,可以获取更丰富的信息,如文本、选中区域等。

3.2 获取提交内容

  • onSubmitted: 当用户完成输入(如点击键盘上的“完成”或“搜索”按钮)时触发,返回当前文本值 -1
  • onEditingComplete: 也在用户完成编辑时调用,与 onSubmitted 行为略有不同。它默认会更新 controller 并使焦点失焦 -1

3.3 焦点管理

  • focusNode: 通过 FocusNode 对象可以手动控制输入框的焦点,例如让某个输入框自动获得焦点、监听焦点变化、手动将焦点移动到下一个输入框或关闭键盘 。
// 监听焦点变化
focusNode.addListener((){
  print('焦点状态: ${focusNode.hasFocus}');
});

// 手动请求焦点
FocusScope.of(context).requestFocus(focusNode);

// 收起键盘 (取消所有焦点)
focusNode.unfocus();
  • autofocus: 设置为 true 时,页面加载后输入框会自动获得焦点 -2

4. 样式自定义与综合示例

4.1 自定义边框

默认 TextField 是下划线样式,你可以通过 decoration 中的 borderenabledBorderfocusedBorder 等属性自定义不同状态下的边框样式 -2

TextField(
  decoration: InputDecoration(
    labelText: '密码',
    border: OutlineInputBorder(), // 使用圆形边框
    enabledBorder: OutlineInputBorder( // 未获焦点时的边框
      borderSide: BorderSide(color: Colors.grey, width: 1.0),
    ),
    focusedBorder: OutlineInputBorder( // 获得焦点时的边框
      borderSide: BorderSide(color: Colors.blue, width: 2.0),
    ),
  ),
  obscureText: true,
)

4.2 综合示例:登录表单

以下是一个结合了上述知识点的简单登录表单示例。

import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

class MainPage extends StatefulWidget {
  MainPage({Key? key}) : super(key: key);

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  
  TextEditingController _userNameEditController = TextEditingController();
  TextEditingController _userPwdEditConroller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("登录"),
        ),
        body: Container(
          padding: EdgeInsets.all(10),
          child: Column(
            children: [
              TextField(
                cursorColor: Colors.red, // 光标颜色
                cursorWidth: 3.0,       // 光标宽度
                cursorRadius: Radius.circular(5.0), // 光标圆角
                controller: _userNameEditController,
                onChanged: (value){
                  print("change"+value);
                },
                onSubmitted: (value){
                  print(value);
                },
                decoration: InputDecoration(
                  labelText: '用户名',
                  hintText: "请输入账号",
                  prefixIcon: Icon(Icons.person),
                  contentPadding: EdgeInsets.only(left: 10),
                  fillColor: const Color.fromARGB(255, 236, 236, 234),
                  filled: true,     // 是否使用自定义颜色 
                  border: OutlineInputBorder(
                    borderSide: BorderSide.none,
                    borderRadius: BorderRadius.circular(25)
                  ),
                  enabledBorder: OutlineInputBorder( // 未获焦点时的边框
                    borderSide: BorderSide(color: Colors.grey, width: 1.0),
                  ),
                  focusedBorder: OutlineInputBorder( // 获得焦点时的边框
                    borderSide: BorderSide(color: Colors.blue, width: 2.0),
                  ),
                ),
              ),
              SizedBox(
                height: 20,
              ),
              TextField(
                controller: _userPwdEditConroller,
                obscureText: true,  // 不显示实际内容
                decoration: InputDecoration(
                  hintText: "请输入密码",
                  suffixIcon: Icon(Icons.visibility),
                  contentPadding: EdgeInsets.only(left: 10),
                  fillColor: const Color.fromARGB(255, 236, 236, 234),
                  filled: true,
                  border: OutlineInputBorder(
                    borderSide: BorderSide.none,
                    borderRadius: BorderRadius.circular(25)
                  )
                ),
              ),
              SizedBox(
                height: 20,
              ),
              TextButton(onPressed: (){
                print("${_userNameEditController.text}");
              }, child: Text("登录"))
            ],
          ),
        )
      ),
    );
  }
}