上面代码中,都行需要维护组件的状态,所以继承自 StatefulWidget ,在 build 中,构建了 checkBox 和 Switch 和 Radio,在点击的时候修改状态,然后重新构建 UI
属性
共有属性 activeColor,设置激活状态的颜色
宽高:Checkbox 无法自定义,Switch 只能定义宽度
Checkbox 有一个属性 tristate,表示是否为三态,默认值为false,如果为true 时,valude 的值会自动增加一个状态 null
总结
Switch , Checkbox 和 Radio 本身不会维护状态,而是需要父组件来管理状态,当用户点击时,通过事件将状态通知到父组件,因此是否选中就会和用户数据发生关联,而这些用户数据也不是他们的私有状态。因此,我们在自定义组件是应该思考一下那种方式最为合理
输入框和表单
Material 组件库中提供了输入框组件 TextField 和表单组件 From ,下面来具体看一下
TextField
用于文本输入,它提供了很多属性,首先简单看一下关键的属性作用
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, ... })
controller :编辑框的控制器,可以通过它设置/获取编辑框的内容,选择编辑框的内容,监听编辑框文本改变事件。大多数情况下我们都需要显示的提供一个 controller 来与文本框交互,如果没有提供,则 TextField 会自动创建一个
focusNode :用于控制 TextField 是否占有当前键盘输入的焦点、他是我们和键盘交互的一个句柄(handler)。
InputDecoration:用于控制 TextField 的外观显示,如提示文本,背景颜色,边框等
keyboardType :用于设置该输入框的键盘输入类型,取值如下:
textInputAction :键盘动作按钮图标,他是一个枚举值,有多个可选值,具体的可查看 api
style:正在编辑的文本样式
textAlign:输入框内编辑文本在水平方向的对齐方式
autofocus:是否自动获取焦点.
obscureText :是否隐藏正在编辑的文本,如输入密码等。
maxLines :输入最大行数,默认为 1,如果为 null,则为无限制maxLength 和 maxLengthEnforced :前者代表输入文本的最大长度,设置后输入框右下角会显示输入的文本计数。后者决定输入长度超过 maxLength 后是否阻止
onChange:输入框内容改变的回调,也可通过 controller 来监听
onEditingComplete 和 onSubmitted :这两者都是在输入完成时触发,例如点击键盘的完成,或者搜索等。不同的是后者的回调是 ValueChanged ,前者不接受参数
inputFormatters:用于指定输入格式,当输入内容改变时,会根据指定格式来校验
enable:若为 false,则输入框被禁用
cursorWidth ,cursorRadius 和 cursorColor:定义光标的宽度,圆角和颜色
栗子
class InputText extends StatefulWidget { @override State<StatefulWidget> createState() { return _InputText(); } } class _InputText extends State<InputText> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("输入框"), ), body: Column( children: [ TextField( autocorrect: true, decoration: InputDecoration( labelText: "用户名", hintText: "用户名或邮箱", prefixIcon: Icon(Icons.person)), ), TextField( decoration: InputDecoration( labelText: "密码", hintText: "您的登录密码", prefixIcon: Icon(Icons.lock)), obscureText: true, ) ], ), ); } }
获取输入内容
1,定义两个变量,则 onChange 触发的时候保存即可
2,通过 controler 直接获取
//定义一个controller TextEditingController _nameController = TextEditingController(); 1 2 TextField( autofocus: true, controller: _nameController, //设置controller ... )
//直接输出即可 print(_nameController.text)
controller 还可以用来设置默认值,选择文本等
_nameController.text="hello world!"; _nameController.selection=TextSelection( baseOffset: 2, extentOffset: _nameController.text.length ); TextField( controller: _nameController, )
控制焦点
焦点可以通过 FocusNode 和 FocusScopeNode 来控制,默认情况下,焦点由FocusScope来管理,它代表焦点控制范围,可以在这个范围内可以通过FocusScopeNode在输入框之间移动焦点、设置默认焦点等。我们可以通过FocusScope.of(context) 来获取Widget树中默认的FocusScopeNode。
简单焦点状态改变事件
// 创建 focusNode FocusNode focusNode = new FocusNode(); ... // focusNode绑定输入框 TextField(focusNode: focusNode); ... // 监听焦点变化 focusNode.addListener((){ print(focusNode.hasFocus); });
自定义样式
隐藏文本
TextField( obscureText: true, )
隐藏后输入的内容将不可见,变成密码类型了
键盘类型
TextField( keyboardType: TextInputType.number, ),
例如,number 就只能输入数字,还有很多的值,如下,可以自行查看
键盘按钮
即键盘右下角的按钮,常见的例如完成,是一个对号的按钮等
大小写
控制英文字母的大小写,比如但是首字母大写等
TextField( textCapitalization: TextCapitalization.words, ),
textCapitalization 的选项
1,words:单词首字母大写
2,sentences:句子首字母大写
3,characters:所有字母大写
4,none:默认无
光标
TextField( cursorColor: Colors.orange,//颜色 cursorWidth: 15,//宽度 cursorRadius: Radius.circular(15),//圆角 ),
计数器
TextField( maxLength: 11, ),
设置最大长度计数器就可显示出来
自定义计数器/图标
TextField( autocorrect: true, maxLength: 11, controller: _nameController, decoration: InputDecoration( labelText: "用户名", hintText: "用户名或邮箱", counter: Text("计数器 0/100"), prefixIcon: Icon(Icons.person)), ),
通过 counter 来实现 自定义计数器
并且通过 prefixIcon 可以设置左侧内图标,通过 icon 可设置左侧外图标
decoration: InputDecoration( suffixIcon: IconButton( icon: Icon(Icons.close), onPressed: () { controller.clear(); }, ),
通过 suffixIcon 可以设置右侧内图标,并且可以设置点击事件
错误文字提示
TextField( controller: controller, decoration: InputDecoration( errorText: "请输入内容", ), ),
去除下划线
decoration: InputDecoration.collapsed(hintText: "用户名或邮箱")),
边框
decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(15)),//圆角 borderSide: BorderSide(color: Colors.red, width: 2.0)),//颜色,宽度 ),
颜色使用的是主题颜色,//TODO 这里设置的不生效,日后解决
表单 Form
在实际开发中,在请求接口之前会对输入框中的数据进行校验,如果对每个 TextField 都进行校验会非常麻烦,为此,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,则会返回到上一个路由,此属性通常用于拦截按钮
onChange:Form 的任意一个字 FormField 内容变化时都会触发此回调
FormField
Form 的子孙元素必须是 FormField 类型,FormField 是一个抽象类,有几个属性,FormState 通过他们来完成操作,FormField 部分定义如下:
const FormField({ ... FormFieldSetter<T> onSaved, //保存回调 FormFieldValidator<T> validator, //验证回调 T initialValue, //初始值 bool autovalidate = false, //是否自动校验。 })
为了方便使用,Flutter 提供了一个 TextFormField 组件,他继承自 FormField 类,也是一个包装类,所以除了 FormField 之后,它还包括 TextField 的属性
FormState
FormState 为 Form 的 State 类,可以通过 Form.of() 或者 Globalkey 获得,我们可以通过他来对 Form 的子孙 FormField 进行统一的操作。
FormState.validate():此方法会调用 Form 子孙 FormFile 的1 validate 回调,如果有一个校验失败,则返回 false,所有校验失败的都会返回错误提示
FormState.save():此方法会调用 Form 子孙 FormField 的 save 回调,用于保存表单内容
FormSata.reset():调用此方法后,会将子孙 FormField 的内容清空
栗子
class InputText extends StatefulWidget { @override State<StatefulWidget> createState() { return _InputText(); } } class _InputText extends State<InputText> { //定义一个controller TextEditingController _nameController = TextEditingController(); GlobalKey _formKey = new GlobalKey<FormState>(); @override void initState() { super.initState(); _nameController.addListener(() => print("账号:" + _nameController.text)); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("输入框"), ), body: Padding( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), child: Form( key: _formKey,//设置globalKey,用于后面获取FormState autovalidate: true,//开启自动校验 child: Column( children: [ TextFormField( autocorrect: true, maxLength: 11, controller: _nameController, decoration: InputDecoration.collapsed(hintText: "用户名或邮箱"), validator: (v) { return v.trim().length > 0 ? null : "用户名不能为空"; }, ), TextFormField( decoration: InputDecoration.collapsed(hintText: "您的登录密码"), validator: (v) { return v.trim().length > 5 ? null : "密码不能少于6位"; }, ), Padding( padding: const EdgeInsets.only(top: 28), child: Row( children: [ Expanded( child: RaisedButton( padding: EdgeInsets.all(15), child: Text("登录"), color: Theme.of(context).primaryColor, textColor: Colors.white, onPressed: () { //获取 formState ,调用 validate 验证合法性, if ((_formKey.currentState as FormState) .validate()) { print('验证成功'); } }, ), ) ], ), ) ], ), ), )); } }
在登录按钮的 onPressed 方法中不能通过 Form.of(context ) 来获取,原因是,此处的 context 为 InputText 的 context**,而 Form.of(context) 是根据所指定 context 向根去查找,而 FormState 是在 InputText 的子树中,所以不行。**
正确的做法是通过 Builder 来构建登录按钮,Builder 会将 widget 节点的 context 作为回调参数:
Expanded( child: Builder(builder: (context) { return RaisedButton( padding: EdgeInsets.all(15), child: Text("登录"), color: Theme.of(context).primaryColor, textColor: Colors.white, onPressed: () { //获取 formState ,调用 validate 验证合法性, if ((Form.of(context)).validate()) { print('验证成功'); } }, ); }), )
使用这种方式即可