用户输入
使用按钮和文本字段接受用户输入。
学习构建文本输入、使用 controller 管理文本,以及使用按钮处理用户操作。
你将完成的内容
步骤
1
介绍
介绍
应用会在 Tile widget 中显示用户的猜测,但还需要让用户输入这些猜测的方式。在本课中,使用两个交互 widget 构建该功能:
TextField
和 IconButton。
2
实现回调函数
实现回调函数
为了让用户输入猜测,你将创建一个名为 GuessInput 的专用 widget。首先,为 GuessInput widget 创建基本结构,该结构需要一个回调函数作为参数。将回调函数命名为 onSubmitGuess。
将以下代码添加到你的 main.dart 文件中。
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
@override
Widget build(BuildContext context) {
// You'll build the UI in the next steps.
return Container(); // Placeholder
}
}
final void Function(String) onSubmitGuess; 这一行声明了类中名为 onSubmitGuess 的 final 成员,其类型为 void Function(String)。该函数接受单个 String 参数(用户的猜测),且不返回任何值(由 void 表示)。
此回调表明,实际处理用户猜测的逻辑将在别处编写。对于交互 widget,使用回调函数是良好实践,可保持处理交互的 widget 可复用且与任何具体功能解耦。
到本课结束时,当用户输入猜测时会调用传入的 onSubmitGuess 函数。首先,你需要构建此 widget 的视觉部分。
widget 将如下所示。
3
TextField widget
TextField widget
由于文本字段和按钮并排显示,将它们创建为 Row widget。将 build 方法中的 Container 占位符替换为包含 Expanded TextField 的 Row:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
),
),
),
],
);
}
}
你在之前的课程中见过其中一些 widget:
Row 和 Padding。不过,Expanded
widget 是新的。当 Row(或 Column)的子 widget 被 Expanded
包裹时,它会告诉该子 widget 沿主轴填满所有可用空间(Row 为水平方向,Column 为垂直方向),前提是其他子 widget 尚未占用。这使 TextField 拉伸以占据行中 除 其他 widget 占用空间外的所有空间。
TextField widget 在本课中也是新的,并且是重头戏。这是 Flutter 用于文本输入的基本 widget。
到目前为止,TextField 具有以下配置。
-
它使用圆角边框装饰。请注意,装饰配置与
Container和盒子的装饰方式非常相似。 -
其
maxLength属性设置为 5,因为游戏仅允许 5 个字母的单词猜测。
4
使用 TextEditingController 处理文本
使用 TextEditingController 处理文本
接下来,你需要一种方式来管理用户输入到输入框中的文本。为此,请使用 TextEditingController。
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
// NEW
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
),
),
),
//
],
);
}
}
TextEditingController 用于读取、清除和修改 TextField 中的文本。使用时,将其传入 TextField。
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController, // NEW
),
),
),
],
);
}
}
现在,当用户输入文本时,你可以通过 _textEditingController 捕获它,但你需要知道 何时 捕获。响应输入的最简单方式是使用 TextField.onSubmitted 参数。该参数接受回调,每当文本字段获得焦点时用户在键盘上按下「Enter」键,就会触发该回调。
目前,通过将以下回调添加到 TextField.onSubmitted 来确保其正常工作:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
onSubmitted: (input) {
// NEW
print(_textEditingController.text); // Temporary
},
),
),
),
],
);
}
}
在这种情况下,你可以直接打印传给 onSubmitted 回调的 input,但更好的用户体验是在每次猜测后清除文本:你需要 TextEditingController 来实现这一点。按如下方式更新代码:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
onSubmitted: (_) {
// UPDATED
print(_textEditingController.text); // Temporary
_textEditingController.clear(); // NEW
},
),
),
),
],
);
}
}
5
获取输入焦点
获取输入焦点
通常,你希望特定输入或 widget 在用户无需操作的情况下自动获得焦点。例如,在本应用中,用户唯一能做的就是输入猜测,因此应用启动时 TextField 应自动获得焦点。用户输入猜测后,焦点应保持在 TextField 中,以便输入下一次猜测。
要解决第一个焦点问题,请在 TextField 上设置 autofocus 属性。
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
autofocus: true, // NEW
onSubmitted: (input) {
print(input); // Temporary
_textEditingController.clear();
},
),
),
),
],
);
}
}
第二个问题需要你使用 FocusNode
管理键盘焦点。你可以使用 FocusNode 请求 TextField 获得焦点(在移动端使键盘出现),或了解字段何时具有焦点。
首先,在 GuessInput 类中创建 FocusNode:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode(); // NEW
@override
Widget build(BuildContext context) {
// ...
return Container();
}
}
然后,在 controller 清除后提交 TextField 时,使用 FocusNode 请求焦点:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
autofocus: true,
focusNode: _focusNode, // NEW
onSubmitted: (input) {
print(input); // Temporary
_textEditingController.clear();
_focusNode.requestFocus(); // NEW
},
),
),
),
],
);
}
}
现在,输入文本后按 Enter,你可以继续输入。
6
使用输入
使用输入
最后,你需要处理用户输入的文本。回想一下,GuessInput 的构造函数需要一个名为 onSubmitGuess 的回调。在 GuessInput 中,你需要使用该回调。将 print 语句替换为对该函数的调用。
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
autofocus: true,
focusNode: _focusNode,
onSubmitted: (input) {
onSubmitGuess(_textEditingController.text.trim());
_textEditingController.clear();
_focusNode.requestFocus();
},
),
),
),
],
);
}
}
其余功能由父 widget GamePage 处理。在该类的 build 方法中,在 Column widget 的 children 中 Row widget 下方,添加 GuessInput widget:
class GamePage extends StatelessWidget {
GamePage({super.key});
final Game _game = Game();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
spacing: 5.0,
children: [
for (final guess in _game.guesses)
Row(
spacing: 5.0,
children: [
for (final letter in guess) Tile(letter.char, letter.type),
],
),
GuessInput(
onSubmitGuess: (guess) {
// TODO, handle guess
print(guess); // Temporary
},
),
],
),
);
}
}
目前,这只会打印猜测以证明连接正确。提交猜测需要使用 StatefulWidget 的功能,你将在下一课中完成。
7
按钮
按钮
为改善移动端 UX 并体现常见的 UI 实践,还应有可提交猜测的按钮。
Flutter 内置了许多按钮 widget,例如 TextButton、
ElevatedButton,以及你现在将使用的
IconButton。所有这些按钮(以及许多其他交互 widget)都需要两个参数(除可选参数外):
传给
onPressed的回调函数。-
构成按钮内容的 widget(通常是
Text或Icon)。
在 GuessInput widget 中将图标按钮添加到 row widget 的 children 列表,并为其提供要显示的 Icon
widget。
Icon widget 需要配置;在本例中,
padding 属性将按钮边缘与其包裹的图标之间的内边距设置为零。这会移除默认内边距并使按钮更小。
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Container()),
IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.arrow_circle_up),
onPressed: null,
),
],
);
}
}
IconButton.onPressed 回调应该看起来很熟悉:
class GuessInput extends StatelessWidget {
GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Container()),
IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.arrow_circle_up),
onPressed: () {
onSubmitGuess(_textEditingController.text.trim());
_textEditingController.clear();
_focusNode.requestFocus();
},
),
],
);
}
}
此方法与 TextField 上的 onSubmitted 回调作用相同。
8
回顾
回顾
你完成的内容
以下是你本课构建与学习内容的摘要。使用 TextField 构建了文本输入 widget
你创建了带有用于文本输入的 TextField 的 GuessInput widget。你为其配置了圆角边框、字符限制,并使用 Expanded
使其填满行中的可用空间。
使用 TextEditingController 管理了文本
TextEditingController 让你读取和修改文本字段内容。你使用 .text 捕获用户输入,并在提交后使用 .clear()
清除字段。
控制了输入焦点以打造精致的 UX
你使用 autofocus 在启动时聚焦文本字段,并使用 FocusNode 配合 requestFocus() 在每次猜测后保持焦点。这些细节让你的应用感觉响应迅速且制作精良。
使用回调和按钮处理了用户操作
为响应用户输入,你指定了 onSubmitted 和 onPressed 等回调函数。将回调函数作为构造函数参数传入可保持 widget 可复用且与具体逻辑解耦。
9
自测
自测
用户输入测验
1 / 2-
直接访问 TextField 的 text 属性。
不正确。
TextField 不暴露 text 属性;你需要 controller。
-
使用附加到 TextField 的 TextEditingController。
正确!
TextEditingController 提供 text 属性以读取值,并提供 clear() 方法以重置。
-
监听 onChanged 回调并将值存储在变量中。
不正确。
onChanged 可用于读取,但清除需要 TextEditingController。
-
调用 TextField.getText() 方法。
不正确。
TextField 没有 getText 方法;请改用 TextEditingController。
-
直接调用
TextField.focus()。不正确。
TextField 没有 focus 方法;你需要使用 FocusNode。
-
在运行时将
autofocus属性设置为 true。不正确。
autofocus 属性仅在初始构建时有效,不能用于之后移动焦点。
-
使用 FocusNode 并对其调用
requestFocus()。正确!
FocusNode 让你控制焦点,调用
requestFocus()会将焦点移到其关联的 widget。 -
将 TextField 包裹在 GestureDetector 中并以编程方式点击。
不正确。
焦点不是这样管理的;FocusNode 才是正确方式。
除非另有说明,本文档之所提及适用于 Flutter 3.44.0 版本。本页面最后更新时间:2026-06-18。查看文档源码 或者 为本页面内容提出建议。