Flutter 基礎控件篇-->輸入框(TextField)、表單(Form)

Flutter 的Material組件庫中提供了輸入框組件TextField和表單組件Formgit

輸入框(TextField)

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

    • text:文本輸入鍵盤
    • multiline:多行文本,需和maxLines配合使用(設爲null或大於1)
    • number:數字;會彈出數字鍵盤
    • phone:優化後的電話號碼輸入鍵盤;會彈出數字鍵盤並顯示* #
    • datetime:優化後的日期輸入鍵盤;Android上會顯示: -
    • emailAddress:優化後的電子郵件地址;會顯示@ .
    • url:優化後的url輸入鍵盤; 會顯示/ .
  • textInputAction:鍵盤動做按鈕圖標(即回車鍵位圖標),它是一個枚舉值,有多個可選值。所有取值請參考官方文檔:api.flutter.dev
    示例:當值爲TextInputAction.search時,原生Android系統下鍵盤樣式以下:this

圖片加載失敗!

  • style:正在編輯的文本樣式有關屬性在TextStyle裏設置url

  • textAlign:輸入框內編輯文本在水平方向的對齊方式。
    如:textAlign: TextAlign.right,則是從後往左顯示

  • autofocus:是否自動獲取焦點。值爲true或者false

  • obscureText:是否隱藏正在編輯的文本,如用於輸入密碼的場景等,文本內容會用「•」替換。

  • maxLines:輸入框的最大行數,默認爲1;若是爲null,則無行數限制。

  • maxLengthmaxLengthEnforcedmaxLength表明輸入框文本的最大長度,設置後輸入框右下角會顯示輸入的文本計數。maxLengthEnforced決定當輸入文本長度超過maxLength時是否阻止輸入,爲true時會阻止輸入,爲false時不會阻止輸入但輸入框會變紅。

  • onChanged:輸入框內容改變時的回調函數;注:內容改變事件也能夠經過controller來監聽。

  • onEditingCompleteonSubmitted:這兩個回調都是在輸入框輸入完成時觸發,好比按了鍵盤的完成鍵(對號圖標)或搜索鍵(🔍圖標)。不一樣的是兩個回調簽名不一樣,onSubmitted回調是ValueChanged<String>類型,它接收當前輸入內容作爲參數,而onEditingComplete不接收參數。

  • inputFormatters:用於指定輸入格式;當用戶輸入內容改變時,會根據指定的格式來校驗。

  • cursorWidthcursorRadiuscursorColor:這三個屬性是用於自定義輸入框光標寬度、圓角和顏色的。
    如:cursorColor: Colors.greencursorWidth: 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直接獲取。

第一種方式比較簡單,因此就不說了,重點說第二種方式,以用戶名輸入框爲例:

  1. 定義一個controller
//定義一個controller
TextEditingController _unameController = TextEditingController();
複製代碼
  1. 設置輸入框controller
TextField(
    autofocus: true,
    controller: _unameController, //設置controller
    ...
)
複製代碼
  1. 經過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,
          ),
        ],
      ),
    );
  }
}
複製代碼

運行效果:

圖片加載失敗!

監聽文本變化

監聽文本變化也有兩種方式:

  1. 設置onChanged回調,如:
TextField(
    autofocus: true,
    onChanged: (v) {
      print("onChange: $v");
    }
)
複製代碼
  1. 經過controller監聽,如:
@override
void initState() {
  //監聽輸入改變  
  _unameController.addListener((){
    print(_unameController.text);
  });
}
複製代碼

這兩種方式相比,onChanged是專門用於監聽文本變化,而controller的功能卻多一些,除了能監聽文本變化外,它還能夠設置默認值、選擇文本,

例子:

  1. 建立一個controller
TextEditingController _selectionController =  TextEditingController();
複製代碼
  1. 設置默認值,並從第三個字符開始選中後面的字符:
_selectionController.text="hello world!";
_selectionController.selection=TextSelection(
    baseOffset: 2,
    extentOffset: _selectionController.text.length
);
複製代碼
  1. 設置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,
          ),
        ],
      ),
    );
  }
}
複製代碼

運行效果:

圖片加載失敗!

控制焦點

焦點能夠經過FocusNodeFocusScopeNode來控制。
默認狀況下,焦點由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

表單(Form)

前面介紹的輸入框能夠單個操做,可是每每在開發中,須要多多個輸入框同時進行操做,好比清空全部輸入框的內容。

爲此,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,則會返回到上一個路由。此屬性一般用於攔截返回按鈕。

  • onChangedForm的任意一個子FormField內容發生變化時會觸發此回調。

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

FormStateFormState類,能夠經過Form.of()GlobalKey得到。
咱們能夠經過它來對Form的子孫FormField進行統一操做。

經常使用的三個方法:

  • FormState.validate():調用此方法後,會調用Form子孫FormFieldvalidate回調,若是有一個校驗失敗,則返回false,全部校驗失敗項都會返回用戶返回的錯誤提示。

  • FormState.save():調用此方法後,會調用Form子孫FormFieldsave回調,用於保存表單內容。

  • FormState.reset():調用此方法後,會將子孫FormField的內容清空。

代碼示例

修改上面用戶登陸的示例,在提交以前校驗:

  • 用戶名不能爲空,若是爲空則提示「用戶名不能爲空」。
  • 密碼不能小於6位,若是小於6爲則提示「密碼不能少於6位」。

代碼:

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)來獲取,緣由是,此處的contextFormTestRoutecontext,而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的指向問題。


V_V

相關文章
相關標籤/搜索