前言
Material組件庫中提供了輸入框組件TextField和表單組件Form。
輸入框TextField
接口描述
const TextField({
Key key,
// 編輯框的控制器,通過它可以設置/獲取編輯框的內容、選擇編輯內容、監聽編輯文本改變事件。大多數情況下我們都需要顯式提供一個controller來與文本框交互。如果沒有提供controller,則TextField內部會自動創建一個。
this.controller,
// 用於控制TextField是否占有當前鍵盤的輸入焦點。它是我們和鍵盤交互的一個句柄(handle)。
this.focusNode,
// 用於控制TextField的外觀顯示,如提示文本、背景顏色、邊框等。
this.decoration = const InputDecoration(),
// 用於設置該輸入框默認的鍵盤輸入類型,取值如下:
// text:文本輸入鍵盤; multiline:多行文本,需和maxLines配合使用(設為null或大於1);
// number:數字,會彈出數字鍵盤; phone:優化后的電話號碼輸入鍵盤,會彈出數字鍵盤並顯示“* #”;
// datetime:優化后的日期輸入鍵盤,Android上會顯示“: -”; emailAddress:優化后的電子郵件地址,會顯示“@ .”; url:優化后的url輸入鍵盤,會顯示“/ .”;
TextInputType keyboardType,
// 鍵盤動作按鈕圖標(即回車鍵位圖標),它是一個枚舉值,有多個可選值,全部的取值列表可查看官方API文檔。
this.textInputAction,
this.textCapitalization = TextCapitalization.none,
// 正在編輯的文本樣式。
this.style,
this.strutStyle,
// 輸入框內編輯文本在水平方向的對齊方式。
this.textAlign = TextAlign.start,
this.textAlignVertical,
this.textDirection,
this.readOnly = false,
ToolbarOptions toolbarOptions,
this.showCursor,
// 是否自動獲取焦點。
this.autofocus = false,
// 是否隱藏正在編輯的文本,如用於輸入密碼的場景等,文本內容會用“•”替換。
this.obscureText = false,
this.autocorrect = true,
// 輸入框的最大行數,默認為1;如果為null,則無行數限制。
this.maxLines = 1,
this.minLines,
this.expands = false,
// maxLength代表輸入框文本的最大長度,設置后輸入框右下角會顯示輸入的文本計數。
this.maxLength,
// maxLengthEnforced決定當輸入文本長度超過maxLength時是否阻止輸入,為true時會阻止輸入,為false時不會阻止輸入但輸入框會變紅。
this.maxLengthEnforced = true,
// 輸入框內容改變時的回調函數;注:內容改變事件也可以通過controller來監聽。
this.onChanged,
// 這兩個回調都是在輸入框輸入完成時觸發,比如按了鍵盤的完成鍵(對號圖標)或搜索鍵(🔍圖標)。
// 不同的是兩個回調簽名不同,onSubmitted回調是ValueChanged<String>類型,它接收當前輸入內容做為參數,而onEditingComplete不接收參數。
this.onEditingComplete,
this.onSubmitted,
// 用於指定輸入格式;當用戶輸入內容改變時,會根據指定的格式來校驗。
this.inputFormatters,
// 如果為false,則輸入框會被禁用,禁用狀態不接收輸入和事件,同時顯示禁用態樣式(在其decoration中定義)。
this.enabled,
// 這三個屬性是用於自定義輸入框光標的寬度、圓角和顏色。
this.cursorWidth = 2.0,
this.cursorRadius,
this.cursorColor,
this.keyboardAppearance,
this.scrollPadding = const EdgeInsets.all(20.0),
this.dragStartBehavior = DragStartBehavior.start,
this.enableInteractiveSelection = true,
this.onTap,
this.buildCounter,
this.scrollController,
this.scrollPhysics,
})
代碼示例
class InputTest extends StatefulWidget {
@override
_InputTestState createState() => _InputTestState();
}
class _InputTestState extends State<InputTest> {
// 獲取輸入內容
TextEditingController _uNameController = TextEditingController();
// 監聽文本變化
TextEditingController _selectionController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('輸入框'),
),
body: Container(
child: Column(
children: <Widget>[
// 用戶名輸入框
TextField(
autofocus: true,
decoration: InputDecoration(
labelText: '用戶名',
hintText: '用戶名或郵箱',
prefixIcon: Icon(Icons.person),
),
controller: _uNameController,
),
// 獲取輸入內容
Text('用戶名:' + _uNameController.text),
//密碼輸入框
TextField(
decoration: InputDecoration(
labelText: '密碼',
hintText: '您的登錄密碼',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
// 監聽文本變化
onChanged: (v) {
print('onChanged:$v');
},
),
],
),
),
);
}
}
代碼解讀
獲取輸入內容有兩種方式:
- 定義兩個變量,用於保存用戶名和密碼,然后在onChange觸發時,各自保存一下輸入內容。
- 通過controller直接獲取。
監聽文本變也有兩種方式:
- 設置onChange回調。
- 通過controller監聽。
- 區別:onChanged是專門用於監聽文本變化,而controller的功能卻多一些,除了能監聽文本變化外,它還可以設置默認值、選擇文本。
代碼示例
class FocusTestRoute extends StatefulWidget {
@override
_FocusTestRouteState createState() => _FocusTestRouteState();
}
class _FocusTestRouteState extends State<FocusTestRoute> {
// 控制焦點-焦點控制范圍
FocusNode focusNode1 = FocusNode();
FocusNode focusNode2 = FocusNode();
// 在輸入框中移動焦點
FocusScopeNode focusScopeNode;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('控制焦點'),
),
body: Container(
child: Column(
children: <Widget>[
// 輸入框1
TextField(
autofocus: true,
// 關聯focusNode1
focusNode: focusNode1,
decoration: InputDecoration(
labelText: 'input1'
),
),
// 輸入框2
TextField(
// 關聯focusNode2
focusNode: focusNode2,
decoration: InputDecoration(
labelText: 'input2'
),
),
Builder(builder: (ctx) {
return Column(
children: <Widget>[
// 移動焦點按鈕
RaisedButton(
child: Text('移動焦點'),
onPressed: () {
// 將焦點從第一個輸入框移動到第二個
if(null == focusScopeNode){
focusScopeNode = FocusScope.of(context);
}
focusScopeNode.requestFocus(focusNode2);
// 第二種寫法
// FocusScope.of(context).requestFocus(focusNode2);
},
),
// 隱藏鍵盤按鈕
RaisedButton(
child: Text('隱藏鍵盤'),
onPressed: () {
// 當所有編輯框都失去焦點時鍵盤就會收起
focusNode1.unfocus();
focusNode2.unfocus();
},
)
],
);
})
],
),
),
);
}
}
代碼解讀
焦點控制
- 焦點可以通過FocusNode和FocusScopeNode來控制。
- 默認情況下,焦點由FocusScope來管理,它代表焦點控制范圍,可以在這個范圍內可以通過FocusScopeNode在輸入框之間移動焦點、設置默認焦點等。
- 可以通過FocusScope.of(context) 來獲取Widget樹中默認的FocusScopeNode。
表單Form
接口描述
Form({
Key key,
@required this.child,
// 是否自動校驗輸入內容;當為true時,每一個子FormField內容發生變化時都會自動校驗合法性,並直接顯示錯誤信息。否則,需要通過調用FormState.validate()來手動校驗。
this.autovalidate = false,
// 決定Form所在的路由是否可以直接返回(如點擊返回按鈕),該回調返回一個Future對象,如果Future的最終結果是false,則當前路由不會返回;如果為true,則會返回到上一個路由。此屬性通常用於攔截返回按鈕。
this.onWillPop,
// Form的任意一個子FormField內容發生變化時會觸發此回調。
this.onChanged,
})
TextFormField({
Key key,
this.controller,
String initialValue,
FocusNode focusNode,
InputDecoration decoration = const InputDecoration(),
TextInputType keyboardType,
TextCapitalization textCapitalization = TextCapitalization.none,
TextInputAction textInputAction,
TextStyle style,
StrutStyle strutStyle,
TextDirection textDirection,
TextAlign textAlign = TextAlign.start,
bool autofocus = false,
bool readOnly = false,
ToolbarOptions toolbarOptions,
bool showCursor,
bool obscureText = false,
bool autocorrect = true,
bool autovalidate = false,
bool maxLengthEnforced = true,
int maxLines = 1,
int minLines,
bool expands = false,
int maxLength,
ValueChanged<String> onChanged,
GestureTapCallback onTap,
VoidCallback onEditingComplete,
ValueChanged<String> onFieldSubmitted,
FormFieldSetter<String> onSaved,
FormFieldValidator<String> validator,
List<TextInputFormatter> inputFormatters,
bool enabled = true,
double cursorWidth = 2.0,
Radius cursorRadius,
Color cursorColor,
Brightness keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool enableInteractiveSelection = true,
InputCounterWidgetBuilder buildCounter,
})
代碼示例
class FormTestRoute extends StatefulWidget {
@override
_FormTestRouteState createState() => _FormTestRouteState();
}
class _FormTestRouteState extends State<FormTestRoute> {
//
TextEditingController _uNameController = TextEditingController();
//
TextEditingController _pwdController = TextEditingController();
//
GlobalKey _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('表單'),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Form(
// 設置globalKey,用於后面獲取FormState
key: _formKey,
// 是否自動校驗輸入內容;當為true時,每一個子FormField內容發生變化時都會自動校驗合法性,並直接顯示錯誤信息。
// 否則,需要通過調用FormState.validate()來手動校驗。
autovalidate: true,
child: Column(
children: <Widget>[
// 用戶名表單
TextFormField(
autofocus: true,
controller: _uNameController,
// 校驗用戶名
validator: (v) {
return v
.trim()
.length > 0 ? null : '用戶名不能為空!';
},
decoration: InputDecoration(
labelText: '用戶名',
hintText: '用戶名或郵箱',
icon: Icon(Icons.person),
),
),
// 密碼表單
TextFormField(
controller: _pwdController,
obscureText: true,
// 校驗密碼
validator: (v) {
return v
.trim()
.length > 5 ? null : '密碼不能少於6位!';
},
decoration: InputDecoration(
labelText: '密碼',
hintText: '您的登錄密碼',
icon: Icon(Icons.lock),
),
),
// 登錄按鈕
Padding(
padding: const EdgeInsets.only(top: 28.0),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
padding: EdgeInsets.all(15.0),
child: Text('登錄'),
color: Theme.of(context).primaryColor,
textColor: Colors.white,
onPressed: () {
//驗證通過提交數據
if((_formKey.currentState as FormState).validate()){
print('用戶名:' + _uNameController.text);
print('密碼:' + _pwdController.text);
}
else
print('輸入不合法!');
},
),
)
],
),
),
],
),
),
),
);
}
}
代碼解讀
關於Form.of(context)
注意,登錄按鈕的onPressed方法中不能通過Form.of(context)來獲取,原因是,此處的context為FormTestRoute的context,而Form.of(context)是根據所指定context向根去查找,而FormState是在FormTestRoute的子樹中,所以不行。正確的做法是通過Builder來構建登錄按鈕,Builder會將widget節點的context作為回調參數。