一、 輸入框
類似UITextField 與 UITextView結合體,
1.屬性大概介紹總結:
controller:編輯框的控制器,通過它可以設置/獲取編輯框的內(nèi)容、選擇編輯內(nèi)容、監(jiān)聽編輯文本改變事件。大多數(shù)情況下我們都需要顯式提供一個controller來與文本框交互。如果沒有提供controller,則TextField內(nèi)部會自動創(chuàng)建一個。
focusNode:用于控制TextField是否占有當前鍵盤的輸入焦點。
InputDecoration:用于控制TextField的外觀顯示,如提示文本、背景顏色、邊框等。
keyboardType:用于設置該輸入框默認的鍵盤輸入類型,類似UITextField的鍵盤類型,可自行測試。
style:正在編輯的文本樣式。
textAlign: 輸入框內(nèi)編輯文本在水平方向的對齊方式。
autofocus: 是否自動獲取焦點,若為YES,即鍵盤彈出成為第一響應者。
obscureText:輸入密碼用的類型,輸入后變成小黑點。
maxLines:輸入框的最大行數(shù),默認為1;如果為null,則無行數(shù)限制。
maxLength :代表輸入框文本的最大長度,設置后輸入框右下角會顯示輸入的文本計數(shù)。
maxLengthEnforced決定當輸入文本長度超過maxLength時是否阻止輸入,為true時會阻止輸入,為false時不會阻止輸入但輸入框會變紅。
onChange:輸入框內(nèi)容改變時的回調(diào)函數(shù);注:內(nèi)容改變事件也可以通過controller來監(jiān)聽。
onEditingComplete和onSubmitted:這兩個回調(diào)都是在輸入框輸入完成時觸發(fā),比如按了鍵盤的完成鍵(對號圖標)或搜索鍵.
inputFormatters:用于指定輸入格式;當用戶輸入內(nèi)容改變時,會根據(jù)指定的格式來校驗。
enable:如果為false,則輸入框會被禁用,禁用狀態(tài)不接收輸入和事件,同時顯示禁用態(tài)樣式(在其decoration中定義)。
cursorWidth、cursorRadius和cursorColor:自定義輸入框光標寬度、圓角和顏色的。
2.實踐結果嘗試

3. 核心測試代碼
class _FlutterTextFieldFormState extends State<FlutterTextField> {
TextEditingController _textEditingController = TextEditingController();
// 每一個輸入框都有一個FocusNode與之對應
var _focusTextFieldNode = FocusNode();
var _focusPwdFieldNode = FocusNode();
var _focusCustomFieldNode = FocusNode();
FocusScopeNode focusScopeNode;
@override
void initState() {
super.initState();
// 初始化字符串,與選中字符
_textEditingController.text = '123er';
_textEditingController.selection =
TextSelection(baseOffset: 0, extentOffset: 3);
//監(jiān)聽輸入改變
_textEditingController.addListener(() {
print('Controller監(jiān)聽:${_textEditingController.text}');
});
_focusTextFieldNode.addListener((){
print('監(jiān)聽焦點變化 : ${_focusTextFieldNode.hasFocus}');
});
// 這里監(jiān)聽文本成為第一響應者,從而更新底邊顏色
_focusCustomFieldNode.addListener((){
setState(() {
});
});
}
Widget _buildTextField(context) {
return Theme(
data: Theme.of(context).copyWith(
hintColor: Colors.red,
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(
color: Colors.yellow
),
hintStyle: TextStyle(
color: Colors.purple,
fontSize: 20
)
)
),
child: TextField(
controller: _textEditingController,
focusNode: _focusTextFieldNode,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
style: TextStyle(
color: Colors.red,
fontSize: 30,
),
textAlign: TextAlign.center,
autofocus: true,
obscureText: false,
maxLines: 1,
maxLength: 10,
maxLengthEnforced: false,
onChanged: (text) {
print(text);
},
onEditingComplete: () {
print('完成后:${_textEditingController.text}');
},
onSubmitted: (text) {
print('提交點擊');
_focusTextFieldNode.unfocus();
},
//List<TextInputFormatter> inputFormatters,
enabled: true,
cursorWidth: 2.0,
cursorRadius: Radius.circular(20.0),
cursorColor: Colors.cyan,
decoration: InputDecoration(
labelText: "用戶名",
hintText: "用戶名或郵箱",
prefixIcon: Icon(Icons.person),
icon: Icon(
Icons.directions_run,
color: Colors.red,
size: 40,
),
),
),
);
}
Widget _buidlCustomField(context){
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: _focusCustomFieldNode.hasFocus ? Colors.lightBlueAccent : Colors.green[200],
width: 5.0,
)
)
),
child: TextField(
focusNode: _focusCustomFieldNode,
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "Email",
hintText: "電子郵件地址",
prefixIcon: Icon(Icons.email),
border: InputBorder.none //隱藏下劃線
),
),
);
}
Widget _buildPwdField() {
return TextField(
focusNode: _focusPwdFieldNode,
decoration: InputDecoration(
labelText: 'password',
hintText: '輸入你的密碼',
prefixIcon: Icon(Icons.arrow_drop_down),
),
obscureText: true,
);
}
Widget _buildFeatherButton(context){
return Builder(builder: (ctx)
{
return Column(
children: <Widget>[
RaisedButton(
child: Text("移動焦點"),
onPressed: () {
if (null == focusScopeNode) {
focusScopeNode = FocusScope.of(context);
}
print(focusScopeNode);
focusScopeNode.requestFocus(_focusPwdFieldNode);
},
),
RaisedButton(
child: Text("隱藏鍵盤"),
onPressed: () {
_focusTextFieldNode.unfocus();
_focusPwdFieldNode.unfocus();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextField'),
backgroundColor: Colors.orange,
),
body: Center(
child: Column(
children: <Widget>[
_buildTextField(context),
_buildPwdField(),
_buildFeatherButton(context),
_buidlCustomField(context),
],
),
),
);
}
}
4. 補充:控制焦點
- 焦點可以通過FocusNode和FocusScopeNode來控制。
- 默認情況下,焦點由FocusScope來管理,它代表焦點控制范圍,可以在這個范圍內(nèi)可以通過FocusScopeNode在輸入框之間移動焦點、設置默認焦點等。
- 通過FocusScope.of(context) 來獲取widget樹中默認的FocusScopeNode。
二、表單
Form widget,它可以對輸入框進行分組,然后進行一些統(tǒng)一操作,如輸入內(nèi)容校驗、輸入框重置以及輸入內(nèi)容保存。
Form({
@required Widget child,
bool autovalidate = false,
WillPopCallback onWillPop,
VoidCallback onChanged,
})
autovalidate:是否自動校驗輸入內(nèi)容;當為true時,每一個子FormField內(nèi)容發(fā)生變化時都會自動校驗合法性,并直接顯示錯誤信息。否則,需要通過調(diào)用FormState.validate()來手動校驗。
onWillPop:決定Form所在的路由是否可以直接返回(如點擊返回按鈕),該回調(diào)返回一個Future對象,如果Future的最終結果是false,則當前路由不會返回;如果為true,則會返回到上一個路由。此屬性通常用于攔截返回按鈕。
onChanged:Form的任意一個子FormField內(nèi)容發(fā)生變化時會觸發(fā)此回調(diào)。
1. 與Form相關的幾個類型
FormField: Form的子孫元素必須是FormField類型,F(xiàn)ormField是一個抽象類,定義幾個屬性,F(xiàn)ormState內(nèi)部通過它們來完成操作,F(xiàn)ormField部分定義如下:
const FormField({
...
FormFieldSetter<T> onSaved, //保存回調(diào)
FormFieldValidator<T> validator, //驗證回調(diào)
T initialValue, //初始值
bool autovalidate = false, //是否自動校驗。
})
為了方便使用,F(xiàn)lutter提供了一個TextFormField widget,它繼承自FormField類,也是TextField的一個包裝類,所以除了FormField定義的屬性之外,它還包括TextField的屬性。
FormState: FormState為Form的State類,可以通過Form.of()或GlobalKey獲得。我們可以通過它來對Form的子孫FormField進行統(tǒng)一操作。我們看看其常用的三個方法:
FormState.validate():調(diào)用此方法后,會調(diào)用Form子孫FormField的validate回調(diào),如果有一個校驗失敗,則返回false,所有校驗失敗項都會返回用戶返回的錯誤提示。
FormState.save():調(diào)用此方法后,會調(diào)用Form子孫FormField的save回調(diào),用于保存表單內(nèi)容
FormState.reset():調(diào)用此方法后,會將子孫FormField的內(nèi)容清空。
2. 測試案例運行結果

3. 核心測試代碼
因為有狀態(tài)變更,所以依然需要繼承自State,監(jiān)聽并更新。
class _FlutterFromState extends State<FlutterForm> {
// TextEditingController用來監(jiān)聽文本
TextEditingController _accountEditController = TextEditingController();
TextEditingController _pwdEditController = TextEditingController();
// 設置globalKey,用于后面獲取FormState
GlobalKey _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Form'),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
child: Form(
key: _formKey,
autovalidate: true,
child: Column(
children: <Widget>[
_buildAccountTF(),
_buildPwdTF(),
_buildLoginButton(),
_buildClearButton(),
],
),
),
),
);
}
Widget _buildAccountTF() {
return TextFormField(
autofocus: true,
controller: _accountEditController,
decoration: InputDecoration(
labelText: '用戶名', hintText: '請輸入用戶賬號', icon: Icon(Icons.person)),
// 驗證
validator: (v) {
return v.trim().length < 6 ? '賬號不能小于6位' : null;
},
);
}
Widget _buildPwdTF() {
return TextFormField(
autofocus: false,
controller: _pwdEditController,
decoration: InputDecoration(
labelText: '密碼',
hintText: '請輸入密碼',
icon: Icon(Icons.lock),
),
validator: (v) {
return v.trim().length > 0 ? null : '密碼不能為空';
},
);
}
Widget _buildLoginButton() {
return Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
padding: const EdgeInsets.all(5),
child: Text(
'點我登錄',
style: TextStyle(
fontSize: 20,
),
),
textColor: Colors.red,
onPressed: () {
if ((_formKey.currentState as FormState).validate()) {
print('驗證通過,可以登錄');
} else {
print('驗證不通過');
}
}))
],
),
);
}
Widget _buildClearButton() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
padding: const EdgeInsets.all(5),
child: Text(
'清除數(shù)據(jù)',
style: TextStyle(fontSize: 20),
),
onPressed: () {
if (_accountEditController.text.length <= 0 &&
_pwdEditController.text.trim().length <= 0) {
print('沒有數(shù)據(jù)');
return;
}
FormState s = _formKey.currentState as FormState;
s.reset();
})
],
),
);
}
}
4. 注意context參數(shù)
context正是操作Widget所對應的Element的一個接口,由于Widget樹對應的Element都是不同的,所以context也都是不同的,注意此context非彼context。