使用者輸入
使用按鈕和文字欄位接收使用者輸入。
學習建置文字輸入欄位、使用控制器管理文字,以及使用按鈕處理使用者動作。
你將完成的事項
Steps
1
簡介
簡介
應用程式將在 Tile 元件 (Widget) 中顯示使用者的猜測,
但它需要一種方式讓使用者輸入猜測內容。
在本課中,使用兩個互動元件來建置此功能:
TextField
和 IconButton。
2
實作回呼(callback)函式
實作回呼(callback)函式
為了讓使用者能夠輸入猜測內容,
你將建立一個名為 GuessInput 的專用元件。
首先,為你的 GuessInput 元件建立基本結構,
該結構需要一個回呼函式作為引數。
將此回呼函式命名為 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 表示)。
此回呼告訴我們,實際處理使用者猜測的邏輯將在其他地方撰寫。 對互動元件而言,使用回呼函式是一種良好的實踐, 可讓處理互動的元件保持可重複使用, 並與任何特定功能解耦。
在本課結束時,當使用者輸入猜測時,
傳入的 onSubmitGuess 函式將被呼叫。
首先,你需要建置此元件的視覺部分。
以下是元件完成後的外觀。
3
TextField 元件
TextField 元件
由於文字欄位和按鈕並排顯示,
請將它們建立為一個 Row 元件。
將 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)),
),
),
),
),
),
],
);
}
}
你在前幾課中已見過其中一些元件:
Row 和 Padding。不過 Expanded
元件是新的。
當 Row(或 Column)的子元件被 Expanded 包裹時,
它會告訴該子元件填滿主軸方向上的所有可用空間
(Row 為水平方向,Column 為垂直方向),
即其他子元件未佔用的部分。
這使 TextField 延伸以佔用所有空間,除了
同一列中其他元件所佔用的空間。
TextField 元件在本課中也是新的,它是主角。
這是 Flutter 中用於文字輸入的基本元件。
目前,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
取得輸入焦點
取得輸入焦點
通常,你希望某個特定的輸入或元件
能夠自動取得焦點,而無需使用者採取任何動作。
例如在這個應用程式中,使用者唯一能做的事就是輸入猜測,
因此 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();
}
}
然後,在控制器清除後每當 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();
},
),
),
),
],
);
}
}
其餘功能由父元件 GamePage 處理。
在該類別的 build 方法中,
在 Column 元件子元件清單的 Row 元件之後,
新增 GuessInput 元件:
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 內建了許多按鈕元件,例如 TextButton、
ElevatedButton,以及你現在將使用的:IconButton。
所有這些按鈕(以及許多其他互動元件)需要兩個
引數(除了其可選引數之外):
- 傳遞給
onPressed的回呼函式。 - 組成按鈕內容的元件(通常為
Text或Icon)。
在 GuessInput 元件的 Row 元件子元件清單中新增一個圖示按鈕,
並為其提供一個 Icon
元件來顯示。
Icon 元件需要設定;在這個情況下,
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 建置文字輸入元件
你建立了一個帶有 TextField 的 GuessInput 元件用於文字輸入。 你為其設定了圓角邊框、字元限制,並 使用 Expanded
使其填滿列中的可用空間。
使用 TextEditingController 管理文字
TextEditingController 讓你可以讀取和修改文字欄位的內容。 你使用它透過 .text 擷取使用者的輸入,並在 提交後透過 .clear()
清除欄位。
控制輸入焦點以提升使用者體驗
你使用 autofocus 在啟動時聚焦文字欄位,並使用帶有 requestFocus() 的 FocusNode 在每次猜測後維持焦點。 這些細節讓你的應用程式感覺更靈敏且完善。
使用回呼和按鈕處理使用者動作
為了回應使用者的輸入, 你指定了 onSubmitted 和 onPressed 等回呼函式。 將回呼函式作為建構函式引數傳遞,可讓你的 元件保持可重複使用,並與特定邏輯解耦。
9
自我測驗
自我測驗
使用者輸入測驗
1 / 2-
監聽 onChanged 回呼並將值儲存在變數中。
Not quite
雖然 onChanged 可用於讀取,但清除需要 TextEditingController。
-
直接存取 TextField 的 text 屬性。
Not quite
TextField 沒有公開 text 屬性;你需要一個控制器。
-
呼叫 TextField.getText() 方法。
Not quite
TextField 沒有 getText 方法;請改用 TextEditingController。
-
使用附加到 TextField 的 TextEditingController。
That's right!
TextEditingController 提供 text 屬性來讀取值,以及 clear() 方法來重置它。
-
在執行時將 `autofocus` 屬性設為 true。
Not quite
'autofocus' 屬性只在初始建置時有效,無法用於之後移動焦點。
-
將 TextField 包裹在 GestureDetector 中並以程式方式點擊。
Not quite
這不是管理焦點的方式;FocusNode 才是正確的方法。
-
使用 FocusNode 並對其呼叫 `requestFocus()`。
That's right!
FocusNode 讓你控制焦點,呼叫 `requestFocus()` 可將焦點移到其關聯的元件。
-
直接呼叫 `TextField.focus()`。
Not quite
TextField 沒有 focus 方法;你需要使用 FocusNode。
Unless stated otherwise, the documentation on this site reflects Flutter 3.44.0. Page last updated on 2026-06-14. View source or report an issue.