Flutter 中接受使用者文字輸入的元件,最基本的就是 TextField
了,雖說如此,TextField
上可用的特性也是很多,先來個基本的吧!
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Openhome.cc'),
),
body: SearchField()
)
)
);
class SearchField extends StatefulWidget {
@override
State<StatefulWidget> createState() => _SearchField();
}
class _SearchField extends State<SearchField> {
@override
Widget build(BuildContext context) {
return TextField(
decoration: InputDecoration(
labelText: '搜尋',
hintText: '搜尋文件',
prefixIcon: Icon(Icons.search),
),
textInputAction: TextInputAction.search,
onSubmitted: (value) => searchDialog(context, value)
);
}
}
void searchDialog(context, value) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("搜尋 $value"),
content: Text("無搜尋結果"),
actions: [
FlatButton(
child: Text("重新搜尋"),
onPressed: () => Navigator.of(context).pop(),
)
]
)
);
}
TextField
的外觀是由 decoration
特性指定,要留意的是,InputDecoration
的 prefixIcon
指定的是 TextField
的圖示,而 textInputAction
的 textInputAction
設定的是手機鍵盤上輸入鍵的圖示,直接來看執行結果好了:
TextField
的 onSubmitted
事件,是在按下輸入鍵後觸發,value
會是 TextField
的文字,範例中使用了對話框,基本上是路由的原理,這之後再來談;TextField
還有 onChanged
事件,每次欄位內容變動時就會觸發。
TextField
的 onEditingComplete
事件,也是在按下輸入鍵後、onSubmitted
前觸發,事件處理器不會接受引數,onEditingComplete
的內部預設行為,會將值送至 TextField
的控制器,然後在鍵盤輸入鍵類型為 "done"
、"go"
、"send"
或 "search"
等時,令 TextField
失焦,若是 "next"
或 "previous"
時則不會失焦,如果你想改變焦點行為,可以自行指定 onEditingComplete
事件。
如果想在 onSubmitted
、onChanged
以外的地方,處理文字欄位的輸入,該怎麼辦?這就要使用 controller
,也就是方才談到的控制器,例如,你想要在按下登入鈕後,取得名稱、密碼欄位的值:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Openhome.cc')),
body: Login(),
)
)
);
class Login extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Login();
}
class _Login extends State<Login> {
final nameController = TextEditingController(); // 建立控制器
final passwdController = TextEditingController(); // 建立控制器
@override
void dispose() {
nameController.dispose(); // 釋放控制器
passwdController.dispose(); // 釋放控制器
super.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: '帳號',
hintText: '使用者名稱或郵件',
prefixIcon: Icon(Icons.person)
),
controller: nameController, // 設定控制器
),
TextField(
decoration: InputDecoration(
labelText: '密碼',
prefixIcon: Icon(Icons.lock),
),
obscureText: true, // 隱藏輸入
controller: passwdController, // 設定控制器
),
FlatButton(
child: Text('登入'),
onPressed: () {
// 從控制器取得值
print('名稱:${nameController.text}');
print('密碼:${passwdController.text}');
},
)
],
)
);
}
}
TextField
的 controller
可設定控制器,想取得對應欄位的輸入值,可透過控制器的 text
特性,Flutter 官方文件中談到,如果使用了控制器,記得要在 dispose
中釋放控制器;如果你沒有設置 controller
,TextField
的內部會自行建立一個,不過你無法取得這個自行建立的控制器。
來看一下執行結果:
另外,範例中的 SingleChildScrollView
主要是為了避免以下的問題:
雖然使用 Expanded
也可以避免繪製空間不夠的問題,不過 Expanded
會分配可繪製空間,而鍵盤出現時,可繪製空間變小,文字欄位、按鈕等也就會變小,這不是我們想要的行為,因此這邊使用的是 SingleChildScrollView
,簡單來說,這個元件會將列出的 child
元件,以線性的捲動方式來繪製。
onSubmitted
、onChanged
只能註冊一個事件處理器,如果程式中有多個地方,對同一個 TextField
的這些事件有興趣,可以透過 TextEditingController
的 addListener
註冊處理器,TextField
每次狀態改變,像是焦點變動、輸入變更等,都會呼叫註冊的處理器。
如果對焦點變動的事件有興趣,可以透過 FocusNode
的 addListener
註冊處理器,FocusNode
也用來控制元件焦點,例如,在上例中,如果在「帳號」欄位時,按下鍵盤輸入鍵,預設動作只是失去焦點,使用者還得自行點選下個欄位,若想在這個時候,直接將焦點移至「密碼」欄位,可以如下:
import 'package:flutter/material.dart';
同前...略
class _Login extends State<Login> {
final nameController = TextEditingController();
final passwdController = TextEditingController();
final passwdFocus = FocusNode(); // 焦點
@override
void dispose() {
nameController.dispose();
passwdController.dispose();
passwdFocus.dispose(); // 釋放焦點
super.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: '帳號',
hintText: '使用者名稱或郵件',
prefixIcon: Icon(Icons.person)
),
controller: nameController,
// 焦點移至 passwdFocus
onSubmitted: (_) => FocusScope.of(context).requestFocus(passwdFocus),
),
TextField(
decoration: InputDecoration(
labelText: '密碼',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
controller: passwdController,
focusNode: passwdFocus, // 設定焦點
),
FlatButton(
child: Text('登入'),
onPressed: () {
print('名稱:${nameController.text}');
print('密碼:${passwdController.text}');
},
)
],
),
);
}
}
執行起來的效果如下: