iOS-Flutter 输入框及表单

246 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 13 天,点击查看活动详情

TextField

文本输入框,类似于iOS的UITextFiled和UITextView的集合。

const TextField({
  ...
  TextEditingController controller, //编辑框的控制器,通过它可以获取、选择、编辑、监听文本改变事件。
  FocusNode focusNode,//是否占有当前键盘的焦点
  InputDecoration decoration = const InputDecoration(),//用于控制TextField的外观显示,本身是一个装饰器,用于修改背景色边框等。
  TextInputType keyboardType,//键盘样式
  TextInputAction textInputAction,//键盘回车样式
  TextStyle style,//正在编辑的文本样式
  TextAlign textAlign = TextAlign.start,//输入框内编辑文本在水平方向的对齐方式
  bool autofocus = false,//是否自动聚焦
  bool obscureText = false,//是否隐藏正在编辑的文本
  int maxLines = 1,//文本最大行数
  int maxLength,//输入的最大内容
  this.maxLengthEnforcement,//超过最大长度后文本显示样式
  ToolbarOptions? toolbarOptions,//长按或鼠标右击出现的菜单
  ValueChanged<String> onChanged,//输入框内容改变时的回调
  VoidCallback onEditingComplete,//输入完成时回调
  ValueChanged<String> onSubmitted,//输入完成时回调,接收参数ValueChanged<String>
  List<TextInputFormatter> inputFormatters,//用于指定输入格式,当用户输入内容改变时,会根据指定的格式来校验。
  bool enabled,//是否禁用,类似于iOS的enable
  this.cursorWidth = 2.0,//光标宽度
  this.cursorRadius,//光标圆角
  this.cursorColor,//光标颜色
  this.onTap,//点击
  ...
})

使用实例:

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,
    ),
  ],
);

需要注意的是,Widget中的组件,大多是通过Controller去控制的,比如在Widget中也提供了比如onChange的方法去监听文本的输入,以及一些其他的用法。

@override
void initState() {
  //监听输入改变  
  _unameController.addListener((){
    print(_unameController.text);
  });
}

//设置默认值
_selectionController.text="hello world!";
//选中后面字符
_selectionController.selection=TextSelection(
    baseOffset: 2,
    extentOffset: _selectionController.text.length
);
//自定义文本框样式
TextField(
  decoration: InputDecoration(
    labelText: "请输入用户名",
    prefixIcon: Icon(Icons.person),
    // 未获得焦点下划线设为灰色
    enabledBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: Colors.grey),
    ),
    //获得焦点下划线设为蓝色
    focusedBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: Colors.blue),
    ),
  ),
),

Form

继承StatefulWidget对象,对应的状态为FormState。

Form({
  required Widget child,
  bool autovalidate = false,//是否自动校验输入内容,为True,每次输入都会去校验
  WillPopCallback onWillPop,//决定Form所在的路由是否可以直接返回,返回一个Future
  VoidCallback onChanged,//Form的任意一个FormField内容变化会触发回调
})
FormField

Form的子元素必须是FormField类型:

const FormField({
  ...
  FormFieldSetter<T> onSaved, //保存回调
  FormFieldValidator<T>  validator, //验证回调
  T initialValue, //初始值
  bool autovalidate = false, //是否自动校验。
})

FormState

FormState为Form的State类,可以通过Form.of()或者GlobalKey获取。

  • FormState.validate():调用此方法后,会调用Form子孙FormField的validate回调,如果有一个校验失败,则返回false
  • FormState.save():会调用子Widget FormState的save回调,用于保存表单内容
  • FormState.reset():会将子孙FormField的内容清空
import 'package:flutter/material.dart';
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 Form(
      key: _formKey, //设置globalKey,用于后面获取FormState
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: Column(
        children: <Widget>[
          TextFormField(
            autofocus: true,
            controller: _unameController,
            decoration: InputDecoration(
              labelText: "用户名",
              hintText: "用户名或邮箱",
              icon: Icon(Icons.person),
            ),
            // 校验用户名
            validator: (v) {
              return v!.trim().isNotEmpty ? 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: ElevatedButton(
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Text("登录"),
                    ),
                    onPressed: () {
                      // 通过_formKey.currentState 获取FormState后,
                      // 调用validate()方法校验用户名密码是否合法,校验
                      // 通过后再提交数据。
                      if ((_formKey.currentState as FormState).validate()) {
                        //验证通过提交数据
                      }
                    },
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

注意,登录按钮的onPressed方法中不能通过Form.of(context)来获取FormState,原因是,此处的contextFormTestRoute的context,而Form.of(context)是根据所指定context向根去查找,而FormState是在FormTestRoute的子树中,所以不行。正确的做法是通过Builder来构建登录按钮,Builder会将widget节点的context作为回调参数:

Expanded(
 // 通过Builder来获取ElevatedButton所在widget树的真正context(Element) 
  child:Builder(builder: (context){
    return ElevatedButton(
      ...
      onPressed: () {
        //由于本widget也是Form的子代widget,所以可以通过下面方式获取FormState  
        if(Form.of(context).validate()){
          //验证通过提交数据
        }
      },
    );
  })
)

其实context正是操作Widget所对应的Element的一个接口,由于Widget树对应的Element都是不同的,所以context也都是不同的,有关context的更多内容会在后面高级部分详细讨论。Flutter中有很多“of(context)”这种方法,读者在使用时一定要注意context是否正确。