Flutter 的Material組件庫中提供了輸入框組件TextField
和表單組件Form
。git
TextField
主要用於文本輸入。api
構造函數以下:bash
const TextField({
...
TextEditingController controller,
FocusNode focusNode,
InputDecoration decoration = const InputDecoration(),
TextInputType keyboardType,
TextInputAction textInputAction,
TextStyle style,
TextAlign textAlign = TextAlign.start,
bool autofocus = false,
bool obscureText = false,
int maxLines = 1,
int maxLength,
bool maxLengthEnforced = true,
ValueChanged<String> onChanged,
VoidCallback onEditingComplete,
ValueChanged<String> onSubmitted,
List<TextInputFormatter> inputFormatters,
bool enabled,
this.cursorWidth = 2.0,
this.cursorRadius,
this.cursorColor,
...
})
複製代碼
不重要的就沒粘app
controller
:編輯框的控制器,經過它能夠設置/獲取編輯框的內容、選擇編輯內容、監聽編輯文本改變事件等。
大多數狀況下咱們都須要顯式提供一個controller
來與文本框交互。若是沒有提供controller
,則TextField
內部會自動建立一個。ide
focusNode
:用於控制TextField
是否佔有當前鍵盤的輸入焦點。它是咱們和鍵盤交互的一個句柄(handle)。函數
InputDecoration
:用於控制TextField
的外觀顯示,如提示文本、背景顏色、邊框等。 源碼示例(給出源碼,本身下去嘗試一下,不作過多解釋)優化
const InputDecoration({
this.icon,
this.labelText,
this.labelStyle,
this.helperText,
this.helperStyle,
this.hintText,
this.hintStyle,
this.hintMaxLines,
this.errorText,
this.errorStyle,
this.errorMaxLines,
this.hasFloatingPlaceholder = true,
this.isDense,
this.contentPadding,
this.prefixIcon,
this.prefix,
this.prefixText,
this.prefixStyle,
this.suffixIcon,
this.suffix,
this.suffixText,
this.suffixStyle,
this.counter,
this.counterText,
this.counterStyle,
this.filled,
this.fillColor,
this.errorBorder,
this.focusedBorder,
this.focusedErrorBorder,
this.disabledBorder,
this.enabledBorder,
this.border,
this.enabled = true,
this.semanticCounterText,
this.alignLabelWithHint,
})
複製代碼
keyboardType
:用於設置該輸入框默認的鍵盤輸入類型,TextInputType
枚舉值以下:ui
maxLines
配合使用(設爲null或大於1)* #
: -
@ .
/ .
textInputAction
:鍵盤動做按鈕圖標(即回車鍵位圖標),它是一個枚舉值,有多個可選值。所有取值請參考官方文檔:api.flutter.dev
示例:當值爲TextInputAction.search
時,原生Android系統下鍵盤樣式以下:this
style
:正在編輯的文本樣式有關屬性在TextStyle
裏設置url
textAlign
:輸入框內編輯文本在水平方向的對齊方式。
如:textAlign: TextAlign.right,
則是從後往左顯示
autofocus
:是否自動獲取焦點。值爲true
或者false
obscureText
:是否隱藏正在編輯的文本,如用於輸入密碼的場景等,文本內容會用「•」替換。
maxLines
:輸入框的最大行數,默認爲1;若是爲null
,則無行數限制。
maxLength
和maxLengthEnforced
:maxLength
表明輸入框文本的最大長度,設置後輸入框右下角會顯示輸入的文本計數。maxLengthEnforced
決定當輸入文本長度超過maxLength
時是否阻止輸入,爲true
時會阻止輸入,爲false
時不會阻止輸入但輸入框會變紅。
onChanged
:輸入框內容改變時的回調函數;注:內容改變事件也能夠經過controller
來監聽。
onEditingComplete
和onSubmitted
:這兩個回調都是在輸入框輸入完成時觸發,好比按了鍵盤的完成鍵(對號圖標)或搜索鍵(🔍圖標)。不一樣的是兩個回調簽名不一樣,onSubmitted
回調是ValueChanged<String>
類型,它接收當前輸入內容作爲參數,而onEditingComplete
不接收參數。
inputFormatters
:用於指定輸入格式;當用戶輸入內容改變時,會根據指定的格式來校驗。
cursorWidth
、cursorRadius
和cursorColor
:這三個屬性是用於自定義輸入框光標寬度、圓角和顏色的。
如:cursorColor: Colors.green
、cursorWidth: 10,
、cursorRadius: Radius.circular(4),
TextField(
keyboardType: TextInputType.url,
decoration: InputDecoration(
labelText: 'xxx',
prefix: Icon(Icons.lock),
// enabled: false
),
textInputAction: TextInputAction.next,
style: TextStyle(color: Colors.red),
textAlign: TextAlign.right,
autofocus: false,
// obscureText: true,
maxLines: null,
maxLength: 10,
maxLengthEnforced: true,
cursorColor: Colors.green,
cursorWidth: 10,
cursorRadius: Radius.circular(4),
),
複製代碼
運行效果:
Column(
children: <Widget>[
TextField(
autofocus: true,
decoration: InputDecoration(
labelText: "用戶名",
hintText: "用戶名或郵箱",
prefixIcon: Icon(Icons.person)
),
),
TextField(
decoration: InputDecoration(
labelText: "密碼",
hintText: "您的登陸密碼",
prefixIcon: Icon(Icons.lock)
),
obscureText: true,
),
],
);
複製代碼
運行效果:
獲取輸入內容有兩種方式:
定義兩個變量,用於保存用戶名和密碼,而後在onChanged
觸發時,各自保存一下輸入內容。
經過controller
直接獲取。
第一種方式比較簡單,因此就不說了,重點說第二種方式,以用戶名輸入框爲例:
controller
://定義一個controller
TextEditingController _unameController = TextEditingController();
複製代碼
controller
:TextField(
autofocus: true,
controller: _unameController, //設置controller
...
)
複製代碼
controller
獲取輸入框內容:print(_unameController.text)
複製代碼
代碼示例:
import 'package:flutter/material.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
//定義一個controller
TextEditingController _unameController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("輸入框"),
),
body: Column(
children: <Widget>[
TextField(
autofocus: true,
controller: _unameController, //設置
decoration: InputDecoration(
labelText: "用戶名",
hintText: "用戶名或郵箱",
prefixIcon: Icon(Icons.person),
),
onChanged: (String) => { //輸入框改變時觸發onChanged
print(_unameController.text) //打印輸入框的內容
},
),
TextField(
decoration: InputDecoration(
labelText: "密碼",
hintText: "您的登陸密碼",
prefixIcon: Icon(Icons.lock)),
obscureText: true,
),
],
),
);
}
}
複製代碼
運行效果:
監聽文本變化也有兩種方式:
onChanged
回調,如:TextField(
autofocus: true,
onChanged: (v) {
print("onChange: $v");
}
)
複製代碼
controller
監聽,如:@override
void initState() {
//監聽輸入改變
_unameController.addListener((){
print(_unameController.text);
});
}
複製代碼
這兩種方式相比,onChanged
是專門用於監聽文本變化,而controller
的功能卻多一些,除了能監聽文本變化外,它還能夠設置默認值、選擇文本,
例子:
controller
:TextEditingController _selectionController = TextEditingController();
複製代碼
_selectionController.text="hello world!";
_selectionController.selection=TextSelection(
baseOffset: 2,
extentOffset: _selectionController.text.length
);
複製代碼
controller
:TextField(
controller: _selectionController,
)
複製代碼
代碼:
class _CategoryPageState extends State<CategoryPage> {
TextEditingController _selectionController = TextEditingController();
@override
Widget build(BuildContext context) {
_selectionController.text = "我好喜歡你吖!";
_selectionController.selection = TextSelection(
baseOffset: 2, extentOffset: _selectionController.text.length);
return Scaffold(
appBar: AppBar(
title: Text("輸入框"),
),
body: Column(
children: <Widget>[
TextField(
controller: _selectionController,
),
],
),
);
}
}
複製代碼
運行效果:
焦點能夠經過FocusNode
和FocusScopeNode
來控制。
默認狀況下,焦點由FocusScope
來管理,它表明焦點控制範圍,能夠在這個範圍內能夠經過FocusScopeNode
在輸入框之間移動焦點、設置默認焦點等。
咱們能夠經過FocusScope.of(context)
來獲取Widget樹中默認的FocusScopeNode
。
示例要求:
建立兩個TextField
,第一個自動獲取焦點,而後建立兩個按鈕:點擊第一個按鈕能夠將焦點從第一個TextField挪到第二個TextField,點擊第二個按鈕能夠關閉鍵盤。
代碼以下:
class FocusTestRoute extends StatefulWidget {
@override
_FocusTestRouteState createState() => new _FocusTestRouteState();
}
class _FocusTestRouteState extends State<FocusTestRoute> {
FocusNode focusNode1 = new FocusNode();
FocusNode focusNode2 = new FocusNode();
FocusScopeNode focusScopeNode;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
autofocus: true,
focusNode: focusNode1,//關聯focusNode1
decoration: InputDecoration(
labelText: "input1"
),
),
TextField(
focusNode: focusNode2,//關聯focusNode2
decoration: InputDecoration(
labelText: "input2"
),
),
Builder(builder: (ctx) {
return Column(
children: <Widget>[
RaisedButton(
child: Text("移動焦點"),
onPressed: () {
//將焦點從第一個TextField移到第二個TextField
// 這是一種寫法 FocusScope.of(context).requestFocus(focusNode2);
// 這是第二種寫法
if(null == focusScopeNode){
focusScopeNode = FocusScope.of(context);
}
focusScopeNode.requestFocus(focusNode2);
},
),
RaisedButton(
child: Text("隱藏鍵盤"),
onPressed: () {
// 當全部編輯框都失去焦點時鍵盤就會收起
focusNode1.unfocus();
focusNode2.unfocus();
},
),
],
);
},
),
],
),
);
}
}
複製代碼
由於我這個模擬器,他不顯示鍵盤,因此當輸入框獲取焦點的時候,下面會顯示一個鍵盤(我這個不顯示)
FocusNode
繼承自ChangeNotifier
,經過FocusNode
能夠監聽焦點的改變事件,如:
...
// 建立 focusNode
FocusNode focusNode = new FocusNode();
...
// focusNode綁定輸入框
TextField(focusNode: focusNode);
...
// 監聽焦點變化
focusNode.addListener((){
print(focusNode.hasFocus);
});
複製代碼
得到焦點時focusNode.hasFocus
值爲true
,失去焦點時爲false
。
前面介紹的輸入框能夠單個操做,可是每每在開發中,須要多多個輸入框同時進行操做,好比清空全部輸入框的內容。
爲此,Flutter提供了一個Form
組件,它能夠對輸入框進行分組,而後進行一些統一操做,如輸入內容校驗、輸入框重置以及輸入內容保存等。
Form
繼承自StatefulWidget
對象,它對應的狀態類爲FormState
。
構造函數以下:
Form({
@required Widget child,
bool autovalidate = false,
WillPopCallback onWillPop,
VoidCallback onChanged,
})
複製代碼
autovalidate
:是否自動校驗輸入內容;當爲true
時,每個子FormField
內容發生變化時都會自動校驗合法性,並直接顯示錯誤信息。不然,須要經過調用FormState.validate()
來手動校驗。
onWillPop
:決定Form
所在的路由是否能夠直接返回(如點擊返回按鈕),該回調返回一個Future
對象,若是Future
的最終結果是false
,則當前路由不會返回;若是爲true
,則會返回到上一個路由。此屬性一般用於攔截返回按鈕。
onChanged
:Form
的任意一個子FormField
內容發生變化時會觸發此回調。
Form
的子孫元素必須是FormField
類型,FormField
是一個抽象類,定義幾個屬性,FormState
內部經過它們來完成操做,
FormField
部分構造函數以下:
const FormField({
...
FormFieldSetter<T> onSaved, //保存回調
FormFieldValidator<T> validator, //驗證回調
T initialValue, //初始值
bool autovalidate = false, //是否自動校驗。
})
複製代碼
爲了方便使用,Flutter提供了一個TextFormField
組件,它繼承自FormField
類,也是TextField
的一個包裝類,因此除了FormField
定義的屬性以外,它還包括TextField
的屬性。
FormState
爲Form
的State
類,能夠經過Form.of()
或GlobalKey
得到。
咱們能夠經過它來對Form
的子孫FormField
進行統一操做。
經常使用的三個方法:
FormState.validate()
:調用此方法後,會調用Form
子孫FormField
的validate
回調,若是有一個校驗失敗,則返回false
,全部校驗失敗項都會返回用戶返回的錯誤提示。
FormState.save()
:調用此方法後,會調用Form
子孫FormField
的save
回調,用於保存表單內容。
FormState.reset()
:調用此方法後,會將子孫FormField
的內容清空。
修改上面用戶登陸的示例,在提交以前校驗:
代碼:
import 'package:flutter/material.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
TextEditingController _unameController = new TextEditingController();
TextEditingController _pwdController = new TextEditingController();
GlobalKey _formKey = new GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Form Test"),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Form(
key: _formKey, //設置globalKey,用於後面獲取FormState
autovalidate: true, //開啓自動校驗
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
controller: _unameController,
decoration: InputDecoration(
labelText: "用戶名",
hintText: "用戶名或郵箱",
icon: Icon(Icons.person)),
// 校驗用戶名
validator: (v) {
return v.trim().length > 0 ? null : "用戶名不能爲空";
}),
TextFormField(
controller: _pwdController,
decoration: InputDecoration(
labelText: "密碼",
hintText: "您的登陸密碼",
icon: Icon(Icons.lock)),
obscureText: true,
//校驗密碼
validator: (v) {
return v.trim().length > 5 ? null : "密碼不能少於6位";
}),
// 登陸按鈕
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: () {
//在這裏不能經過此方式獲取FormState,context不對
//print(Form.of(context));
// 經過_formKey.currentState 獲取FormState後,
// 調用validate()方法校驗用戶名密碼是否合法,校驗
// 經過後再提交數據。
if ((_formKey.currentState as FormState).validate()) {
//驗證經過提交數據
}
},
),
),
],
),
)
],
),
),
),
);
}
}
複製代碼
運行效果:
注意,登陸按鈕的
onPressed
方法中不能經過Form.of(context)
來獲取,緣由是,此處的context
爲FormTestRoute
的context
,而Form.of(context)
是根據所指定context
向根去查找,而FormState
是在FormTestRoute
的子樹中,因此不行。正確的作法是經過Builder
來構建登陸按鈕,Builder
會將widget節點的context
做爲回調參數:
Expanded(
// 經過Builder來獲取RaisedButton所在widget樹的真正context(Element)
child:Builder(builder: (context){
return RaisedButton(
...
onPressed: () {
//因爲本widget也是Form的子代widget,因此能夠經過下面方式獲取FormState
if(Form.of(context).validate()){
//驗證經過提交數據
}
},
);
})
)
複製代碼
必定要注意context
的指向問題。