【Flutter】輸入框和表單(五)

一、 輸入框

類似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.實踐結果嘗試
測試.gif
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. 測試案例運行結果
測試結果.gif
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。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容