Flutter 基礎組件:輸入框和表單


前言

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作為回調參數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM