Loading

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作为回调参数。

posted @ 2019-12-19 11:22  Parzulpan  阅读(1357)  评论(0编辑  收藏  举报