flutter:输入框 TextField

在 Flutter 中,TextField 是最常用的文本输入控件,用于接收用户的文字输入(单行、多行、密码、数字等)。

常用属性

属性 说明
controller TextEditingController 控制输入内容,可获取或设置文本
focusNode 管理焦点,可以手动获取或释放焦点
decoration InputDecoration,控制输入框样式(边框、提示文字、图标等)
keyboardType 键盘类型(如 TextInputType.textTextInputType.numberTextInputType.emailAddress
obscureText 是否隐藏文本(密码输入框用)
maxLength 最大输入长度
maxLines 最大行数,默认 1,设置为 null 或大于 1 可以多行输入
minLines 最小行数
style 文本样式,TextStyle
textAlign 文本对齐方式,TextAlign.left / center / right
cursorColor 光标颜色
cursorHeight 光标高度
enabled 是否可编辑
readOnly 是否只读
onTap 点击输入框触发
onEditingComplete 编辑完成触发,不同于 onSubmitted
inputFormatters 格式化输入,比如只允许数字或限制长度

InputDecoration 常用属性

decoration 用于美化输入框:

属性 说明
hintText 占位提示文字
labelText 标签文字,浮动在上方
helperText 辅助文字
prefixIcon 前缀图标
suffixIcon 后缀图标,如清除按钮
border 输入框边框样式,OutlineInputBorder / UnderlineInputBorder
filled 是否填充背景色
fillColor 填充背景颜色
errorText 错误提示文字

基础代码

import 'package:flutter/material.dart';

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

  @override
  State<InputPage> createState() => _InputPageState();
}

class _InputPageState extends State<InputPage> {
  // 文本消息
  String _message = "";

  // 输入框控制器
  final TextEditingController _controllerName = TextEditingController();
  final TextEditingController _controllerPassword = TextEditingController();

  // 管理焦点
  FocusNode focusNodeName = FocusNode();
  FocusNode focusNodePassword = FocusNode();
  FocusScopeNode? focusScopeNode;

  // 输入框 - 用户名
  Widget _buildName() {
    return TextField(
      // 控制器
      controller: _controllerName,
      // 焦点
      autofocus: true,
      // 焦点管理
      focusNode: focusNodeName,
      // 输入框的样式
      decoration: const InputDecoration(
        // 输入框的标签文本
        labelText: '用户名',
        // 输入框的辅助提示文本
        hintText: '请输入',
        // 输入框的前缀图标
        prefixIcon: Icon(Icons.person),
        // 输入框的后缀图标
        suffixIcon: Icon(Icons.edit),
        // 输入框的边框样式
        border: OutlineInputBorder(),
      ),
      // 输入改变事件
      onChanged: (String value) {
        setState(() {
          _message = value;
        });
      },
      // 提交回车事件
      onSubmitted: (String value) {
        // 隐藏键盘
        focusScopeNode ??= FocusScope.of(context);
        // 请求焦点
        focusScopeNode?.requestFocus(focusNodePassword);
      },
    );
  }

  // 输入框 - 密码
  Widget _buildPassword() {
    return TextField(
      controller: _controllerPassword,
      // 密码显示
      obscureText: true,
      // 焦点管理
      focusNode: focusNodePassword,
      // 输入框的样式
      decoration: const InputDecoration(
        labelText: '密码',
        hintText: '请输入',
        prefixIcon: Icon(Icons.person),
        suffixIcon: Icon(Icons.edit),
        border: OutlineInputBorder(),
      ),
    );
  }

  // 按钮
  Widget _buildButton() {
    return ElevatedButton(
      child: const Text('登录 Now!'),
      onPressed: () {
        setState(() {
          _message =
              'name:${_controllerName.text}, pass:${_controllerPassword.text}';
        });
      },
    );
  }

  // 显示
  Widget _buildMessage() {
    return Text(_message);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('InputPage')),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: [
            _buildName(),
            const SizedBox(height: 10),
            _buildPassword(),
            const SizedBox(height: 10),
            _buildButton(),
            const SizedBox(height: 10),
            _buildMessage(),
          ],
        ),
      ),
    );
  }
}

FocusNode的作用

综合代码

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(
      title: 'FocusNode 示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const FocusDemoPage(),
    );
  }
}

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

  @override
  State<FocusDemoPage> createState() => _FocusDemoPageState();
}

class _FocusDemoPageState extends State<FocusDemoPage> {
  // 定义多个 FocusNode
  final FocusNode _focusNode1 = FocusNode(); // 用于监听焦点变化
  final FocusNode _focusNode2 = FocusNode(); // 主动获取/失去焦点
  final FocusNode _usernameFocus = FocusNode(); // 多输入框切换
  final FocusNode _passwordFocus = FocusNode();
  final FocusNode _focusNode4 = FocusNode(); // 动态 UI 改变

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

    // 1️⃣ 监听焦点变化
    // 在初始化时添加监听器
    _focusNode1.addListener(() {
      if (_focusNode1.hasFocus) {
        debugPrint("输入框1 获得了焦点");
      } else {
        debugPrint("输入框1 失去了焦点");
      }
    });

    // 4️⃣ 动态UI刷新
    // 在初始化时添加监听器
    _focusNode4.addListener(() {
      setState(() {});
    });
  }

  @override
  void dispose() {
    // 释放 FocusNode
    // 如果不释放 FocusNode,可能会导致内存泄漏
    _focusNode1.dispose();
    _focusNode2.dispose();
    _usernameFocus.dispose();
    _passwordFocus.dispose();
    _focusNode4.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("FocusNode 完整示例")),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 1️⃣ 监听焦点变化
            const Text(
              "1. 监听焦点变化",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            TextField(
              focusNode: _focusNode1,
              decoration: const InputDecoration(labelText: "输入框1(监听焦点变化)"),
            ),
            const SizedBox(height: 20),

            // 2️⃣ 主动控制焦点
            const Text(
              "2. 主动获取/失去焦点",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            TextField(
              focusNode: _focusNode2,
              decoration: const InputDecoration(labelText: "输入框2(按钮控制焦点)"),
            ),
            Row(
              children: [
                ElevatedButton(
                  onPressed: () {
                    // 主动获取焦点:调用 requestFocus 方法
                    // FocusScope.of(context): 这是在获取context对应的 FocusScope
                    // requestFocus: 请求焦点
                    // 请求获取焦点之后会出现键盘
                    // 为什么不能用_focusNode2.requestFocus()?
                    // 因为 FocusNode 需要在其对应的 BuildContext 中使用
                    FocusScope.of(context).requestFocus(_focusNode2);
                  },
                  child: const Text("获取焦点"),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () {
                    // 主动失去焦点:调用 unfocus 方法
                    // 失去焦点之后键盘会收起
                    _focusNode2.unfocus();
                  },
                  child: const Text("失去焦点"),
                ),
              ],
            ),
            const SizedBox(height: 20),

            // 3️⃣ 多输入框切换
            const Text(
              "3. 多输入框切换焦点",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            TextField(
              focusNode: _usernameFocus,
              textInputAction: TextInputAction.next,
              decoration: const InputDecoration(labelText: "用户名"),
              // 提交回车事件:
              // _ 表示当前输入框的内容
              onSubmitted: (_) {
                // 请求焦点: 密码输入框
                FocusScope.of(context).requestFocus(_passwordFocus);
              },
            ),
            TextField(
              focusNode: _passwordFocus,
              obscureText: true,
              decoration: const InputDecoration(labelText: "密码"),
            ),
            const SizedBox(height: 20),

            // 4️⃣ 动态 UI 改变
            const Text(
              "4. 根据焦点状态动态改变UI",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            TextField(
              focusNode: _focusNode4,
              decoration: InputDecoration(
                labelText: "输入框4(获取焦点时变色)",
                border: const OutlineInputBorder(),
                focusedBorder: OutlineInputBorder(
                  borderSide: BorderSide(
                    color: _focusNode4.hasFocus ? Colors.red : Colors.grey,
                    width: 2,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

主要作用:

是否获取焦点

检测输入框是否获取焦点

_focusNode1.addListener(() {
  if (_focusNode1.hasFocus) {
    debugPrint("输入框1 获得了焦点");
  } else {
    debugPrint("输入框1 失去了焦点");
  }
});

TextField(
  focusNode: _focusNode1, // 绑定_focusNode1
  decoration: const InputDecoration(labelText: "输入框1(监听焦点变化)"),
),

手动获取/释放焦点

手动获取/释放焦点:有时候我们需要在点击按钮时,让某个输入框自动弹出键盘,或者收起键盘。

TextField(
  focusNode: _focusNode2,
  decoration: const InputDecoration(labelText: "输入框2(按钮控制焦点)"),
),
Row(
  children: [
    ElevatedButton(
      onPressed: () {
        // 主动获取焦点:调用 requestFocus 方法
        // FocusScope.of(context): 这是在获取context对应的 FocusScope
        // requestFocus: 请求焦点
        // 请求获取焦点之后会出现键盘
        // 为什么不能用_focusNode2.requestFocus()?
        // 因为 FocusNode 需要在其对应的 BuildContext 中使用
        FocusScope.of(context).requestFocus(_focusNode2);
      },
      child: const Text("获取焦点"),
    ),
    const SizedBox(width: 10),
    ElevatedButton(
      onPressed: () {
        // 主动失去焦点:调用 unfocus 方法
        // 失去焦点之后键盘会收起
        _focusNode2.unfocus();
      },
      child: const Text("失去焦点"),
    ),
  ],
),

焦点切换

在多个输入框间切换焦点:比如做表单时,用户输入完“用户名”后,点击下一步会自动跳到“密码”输入框。

TextField(
  focusNode: _usernameFocus,
  textInputAction: TextInputAction.next,
  decoration: const InputDecoration(labelText: "用户名"),
  // 提交回车事件:
  // _ 表示当前输入框的内容
  onSubmitted: (_) {
    // 请求焦点: 密码输入框
    FocusScope.of(context).requestFocus(_passwordFocus);
  },
),
TextField(
  focusNode: _passwordFocus,
  obscureText: true,
  decoration: const InputDecoration(labelText: "密码"),
),

结合 UI 做效果

结合 UI 做效果:可以根据焦点状态来动态改变输入框样式(比如获取焦点时边框变蓝色)。

TextField(
  focusNode: _focusNode4,
  decoration: InputDecoration(
    labelText: "输入框4(获取焦点时变色)",
    border: const OutlineInputBorder(),
    focusedBorder: OutlineInputBorder(
      borderSide: BorderSide(
        color: _focusNode4.hasFocus ? Colors.red : Colors.grey,
        width: 2,
      ),
    ),
  ),
),

TextEditingController的作用

综合代码

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(
      title: 'TextEditingController Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const TextFieldDemoPage(),
    );
  }
}

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

  // createState()方法 在构建TextFieldDemoPage之后就执行
  @override
  State<TextFieldDemoPage> createState() => _TextFieldDemoPageState();
}

class _TextFieldDemoPageState extends State<TextFieldDemoPage> {
  // 1. 创建控制器,并设置初始值
  final TextEditingController _controller = TextEditingController(
    text: "Hello Flutter",
  );

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

    // 2. 监听输入框内容变化
    _controller.addListener(() {
      print("输入内容变化:${_controller.text}");
    });
  }

  @override
  void dispose() {
    _controller.dispose(); // 避免内存泄漏
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("TextEditingController Demo")),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          children: [
            TextField(
              controller: _controller, // 绑定控制器
              decoration: const InputDecoration(
                labelText: "请输入内容",
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),

            // 读取输入内容
            ElevatedButton(
              onPressed: () {
                print("当前输入内容:${_controller.text}");
              },
              child: const Text("读取内容"),
            ),

            // 修改输入内容
            ElevatedButton(
              onPressed: () {
                _controller.text = "新内容设置成功!";
              },
              child: const Text("修改内容"),
            ),

            // 控制光标和选中范围
            ElevatedButton(
              onPressed: () {
                _controller.selection = TextSelection(
                  baseOffset: 0, // 选中范围的起始位置
                  extentOffset: _controller.text.length, // 选中范围的结束位置
                );
              },
              child: const Text("全选文本"),
            ),

            // 清空输入框
            ElevatedButton(
              onPressed: () {
                _controller.clear();
              },
              child: const Text("清空内容"),
            ),
          ],
        ),
      ),
    );
  }
}

主要作用

获取输入内容

获取输入内容:可以通过 controller.text 获取用户在输入框中输入的内容。

// 读取输入内容
ElevatedButton(
  onPressed: () {
    print("当前输入内容:${_controller.text}");
  },
  child: const Text("读取内容"),
),

设置输入内容

设置输入内容:可以主动给输入框赋值,常用于表单初始化或清空输入框。

// 1. 创建控制器,并设置初始值
final TextEditingController _controller = TextEditingController(
  text: "Hello Flutter",
);

controller: _controller, // 绑定控制器

// 清空输入框
ElevatedButton(
  onPressed: () {
    _controller.clear();
  },
  child: const Text("清空内容"),
),

监听输入变化

监听输入变化:通过 addListener 方法可以实时监听输入框内容变化,而不需要依赖 onChanged 回调。

// 2. 监听输入框内容变化
_controller.addListener(() {
  print("输入内容变化:${_controller.text}");
});
}

控制光标位置

控制光标位置 / 选择文本:TextEditingController 还能操作光标和选中文本范围。

// 控制光标和选中范围
ElevatedButton(
  onPressed: () {
    _controller.selection = TextSelection(
      baseOffset: 0, // 选中范围的起始位置
      extentOffset: _controller.text.length, // 选中范围的结束位置
    );
  },
  child: const Text("全选文本"),
),

// 光标移动到末尾
_controller.selection = TextSelection.fromPosition(
  TextPosition(offset: _controller.text.length),
);

×

喜欢就点赞,疼爱就打赏