作一件很容易的小事并不难,困难的是把一件小事作到极致,这就十分不容易了!好比一个登录页,无非就一个logo
、两个输入框以及几个按钮,以下图:html
为何说困难呢?咱们一块儿来看看细节:输入类型、输入长度、是否多行、自动校验、异常提醒、输入法的键盘动做按钮图标(即回车键位图标)修改并实现点击回调、提示文本、输入框样式设置、一键删除等等,接下来咱们就来解决这些问题。前端
import 'package:flutter/material.dart';
class Login extends StatefulWidget {
@override
_LoginState createState() => new _LoginState();
}
class _LoginState extends State<Login> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.fromLTRB(24.0, 0.0, 24.0, 0.0),
child: ListView(
children: <Widget>[
SizedBox(
height: 72,
),
Center(
child: Image.asset(
"res/images/icon_logo.png",
scale: 1.5,
),
),
SizedBox(
height: 36,
),
TextField(
),
SizedBox(
height: 14,
),
TextField(
),
SizedBox(
height: 32,
),
FlatButton(
child: RaisedButton(
padding: EdgeInsets.all(10),
child: Text(
'登陆/注册',
),
)),
],
),
));
}
}
复制代码
丑出天际了吧?我也这么以为,接下来咱们来一一解决上面提出的问题,实现效果图!git
第一步将上面的图标修改一下,设置为一个圆形图形。通过询问百度,得知使用CircleAvatar
能够实现圆形图片,说干就干:github
CircleAvatar(
radius: 56.0,
child: Image.asset(
"res/images/icon_logo.png",
scale: 1.8,
),
)
复制代码
可是后来使用过程当中发现个人图片资源是背景是透明色,而全部的Image
都会默认设置主题颜色为背景色,仍是丑得哭兮流了!正则表达式
将CircleAvatar
的背景色修改成蓝色,而后将图片修改一下,增长一个白色的背景色,代码以下:编程
CircleAvatar(
backgroundColor: Colors.blue,
radius: 56.0,
child: Image.asset(
"res/images/icon_logo.png",
color: Colors.white,
scale: 1.8,
),
),
复制代码
这里须要解决上面提到的问题:api
这些问题看似都很简单,可是细节有魔鬼,看似简单的地方每每埋藏着“炸弹”!接下来咱们就先来看看输入框的Widget
提供了哪些api
供咱们调用?bash
const TextField({
...
this.controller,
this.focusNode,
this.decoration = const InputDecoration(),
TextInputType keyboardType,
this.textInputAction,
this.textCapitalization = TextCapitalization.none,
this.style,
this.strutStyle,
this.textAlign = TextAlign.start,
this.textDirection,
this.autofocus = false,
this.obscureText = false,
this.autocorrect = true,
this.maxLines = 1,
this.minLines,
this.expands = false,
this.maxLength,
this.maxLengthEnforced = true,
this.onChanged,
this.onEditingComplete,
this.onSubmitted,
this.inputFormatters,
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,
this.onTap,
this.buildCounter,
this.scrollPhysics,
...
})
复制代码
- controller:编辑框的控制器,经过它能够设置/获取编辑框的内容、选择编辑内容、监听编辑文本改变事件。大多数状况下咱们都须要显式提供一个controller来与文本框交互。若是没有提供controller,则TextField内部会自动建立一个。
- focusNode:用于控制TextField是否占有当前键盘的输入焦点。它是咱们和键盘交互的一个handle。
- InputDecoration:用于控制TextField的外观显示,如提示文本、背景颜色、边框等。
- keyboardType:用于设置该输入框默认的键盘输入类型,取值以下:
TextInputType枚举值 含义 text 文本输入键盘 multiline 多行文本,需和maxLines配合使用(设为null或大于1) number 数字;会弹出数字键盘 phone 优化后的电话号码输入键盘;会弹出数字键盘并显示"* #" datetime 优化后的日期输入键盘;Android上会显示“: -” emailAddress 优化后的电子邮件地址;会显示“@ .” url 优化后的url输入键盘; 会显示“/ .”
- textInputAction:键盘动做按钮图标(即回车键位图标),它是一个枚举值,有多个可选值,所有的取值列表读者能够查看API文档,下面是当值为
TextInputAction.search
时,原生Android系统下键盘样式:
- style:正在编辑的文本样式。
- textAlign: 输入框内编辑文本在水平方向的对齐方式。
- autofocus: 是否自动获取焦点。
- obscureText:是否隐藏正在编辑的文本,如用于输入密码的场景等,文本内容会用“•”替换。
- maxLines:输入框的最大行数,默认为1;若是为
null
,则无行数限制。- maxLength和maxLengthEnforced :maxLength表明输入框文本的最大长度,设置后输入框右下角会显示输入的文本计数。maxLengthEnforced决定当输入文本长度超过maxLength时是否阻止输入,为true时会阻止输入,为false时不会阻止输入但输入框会变红。
- onChange:输入框内容改变时的回调函数;注:内容改变事件也能够经过controller来监听。
- onEditingComplete和onSubmitted:这两个回调都是在输入框输入完成时触发,好比按了键盘的完成键(对号图标)或搜索键(🔍图标)。不一样的是两个回调签名不一样,onSubmitted回调是
ValueChanged<String>
类型,它接收当前输入内容作为参数,而onEditingComplete不接收参数。- inputFormatters:用于指定输入格式;当用户输入内容改变时,会根据指定的格式来校验。
- enable:若是为
false
,则输入框会被禁用,禁用状态不接收输入和事件,同时显示禁用态样式(在其decoration中定义)。- cursorWidth、cursorRadius和cursorColor:这三个属性是用于自定义输入框光标宽度、圆角和颜色的。
了解了上面的api
之后,咱们就不使用TextField
,就是这么任性!先不要想打人,由于咱们为了自动检测输入的内容是否合法,咱们须要使用一个叫Form
的东西来实现(固然controller
也能够,只是操做起来比较麻烦,像我这样的懒人确定是不会这么作的),并且二者的不少属性都是同样的,不信你看代码:ide
Form(
//设置globalKey,用于后面获取FormState
key: _formKey,
//开启自动校验
autovalidate: true,
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
focusNode: _userFocusNode,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
labelText: "用户名",
hintText: "手机号或邮箱",
icon: Icon(Icons.person),
),
// 校验用户名
validator: (v) {
var userName = v.trim();
if (isChinaPhoneLegal(userName) ||
isEmailValid(userName)) {
return null;
} else {
return "用户名必须是手机号或者邮箱地址";
}
},
onEditingComplete: () {
if (null == focusScopeNode) {
focusScopeNode = FocusScope.of(context);
}
focusScopeNode.requestFocus(_psdFocusNode);
}),
TextFormField(
textInputAction: TextInputAction.done,
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "密码",
hintText: "您的登陆密码",
icon: Icon(Icons.lock),
),
obscureText: true,
//校验密码
validator: (v) {
// 可在此经过正则表达式校验密码是否符合规则
return v.trim().length > 5 ? null : "密码不能少于6位";
},
onEditingComplete: () {
_userFocusNode.unfocus();
_psdFocusNode.unfocus();
},
),
],
),
),
复制代码
目前基本实现了上面说的细节,可是左边的图标并无居中,这个确定逃不过UI的像素眼,因而仔细查看以后,发现InputDecoration
还有两个图标对象,分别是prefixIcon
和suffixIcon
,通过编程合做伙伴有道翻译的帮助,终于明白我在此处使用prefixIcon
就能够圆满解决问题了,此处就不单独上图了。可是还有两个细节没有完成,就是增长一键删除和密码明文显示的控制按钮,可是有了上面编程合做伙伴的帮助,彷佛问题也不难解决了!函数
首先,在Dart
中一切可使用的变量引用都是对象(惋惜我仍是没有对象),所以咱们能够自定义一个对象继承自suffixIcon
,并实现它的点击事件便可解决上面的需求。为何须要自定义一个对象呢?由于若是在同一个类中,经过setState
来刷新是整个类的全局刷新,会把全部的输入框内容所有清除,这不是咱们指望看到的效果。以前看到更新Dialog
的内容也是同一个思路,因此这里咱们就定义了一个StatefulWidget
来实现局部的刷新,代码以下:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
///自带删除的ITextField
typedef void ITextFieldCallBack(String content, bool isValid);
class UserNameField extends StatefulWidget {
@override
State<StatefulWidget> createState() => _UserNameFiledState();
UserNameField(
{@required this.fieldCallBack,
this.focusNode,
this.keyboardType,
this.textInputAction,
this.autofocus,
this.maxLength,
this.onEditingComplete,
this.labelText,
this.hintText,
this.prefixIcon,
this.suffixIcon,
this.validator});
final FocusNode focusNode;
final TextInputType keyboardType;
final TextInputAction textInputAction;
final bool autofocus;
final FormFieldValidator<String> validator;
final ITextFieldCallBack fieldCallBack;
final int maxLength;
final VoidCallback onEditingComplete;
final String labelText;
final String hintText;
final Widget prefixIcon;
final Widget suffixIcon;
}
class _UserNameFiledState extends State<UserNameField> {
bool _isShowCleanIcon = false;
TextEditingController _controller = TextEditingController();
GlobalKey _formKey = new GlobalKey<FormState>();
@override
void initState() {
super.initState();
_controller?.addListener(() {
widget.fieldCallBack(
_controller?.text, (_formKey.currentState as FormState).validate());
bool state = _controller?.text?.length != 0;
if (_isShowCleanIcon != state) {
setState(() {
_isShowCleanIcon = state;
});
}
});
}
@override
Widget build(BuildContext context) {
return Form(
//开启自动校验
autovalidate: true,
key: _formKey,
child: TextFormField(
controller: _controller,
focusNode: widget.focusNode,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
autofocus: widget.autofocus,
maxLength: widget.maxLength,
onEditingComplete: widget.onEditingComplete,
validator: widget.validator,
decoration: InputDecoration(
labelText: widget.labelText,
hintText: widget.hintText,
prefixIcon: widget.prefixIcon,
suffixIcon: GestureDetector(
onTap: () {
widget.fieldCallBack("", false);
setState(() {
_isShowCleanIcon = !_isShowCleanIcon;
});
_controller.clear();
},
child: _isShowCleanIcon
? widget.suffixIcon
: IgnorePointer(
ignoring: true,
child: new Opacity(
opacity: 0.0,
child: widget.suffixIcon,
)),
),
),
),
);
}
}
复制代码
密码明文显示的开关与之相似,此处就不赘述了,后面会提供源码。 接下来看看效果:
Button
的样式flutter
对于按钮提供了多种选择(阿里拍卖前端团队写的Flutter开发者必备手册 Flutter Go介绍了八种,官网介绍的是六种)
咱们的要求很简单,就是一个圆角的长方形Button
便可,这里为了有“水波动画”和实质感的阴影,咱们选择了RaisedButton
来实现:
Padding(
padding: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0),
child: RaisedButton(
color: Colors.blue,
highlightColor: Colors.blue[700],
colorBrightness: Brightness.dark,
splashColor: Colors.grey,
child: Text(
"登陆/注册",
style: TextStyle(color: Colors.white, fontSize: 18.0),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
onPressed: () => {
if (_userNameValid && _psdValid)
{
// TODO 登陆
}
}),
),
复制代码
最后再优化一下,增长忘记密码和跳过登陆,此处不上代码了,直接上图:
说来一个简单的登陆页,写了四个文件,其中两个是自定义Widget
、一个工具类,累计不到400行代码,可是一边查文档一边查资料,写起来仍是各类酸爽!但愿有大佬指出其中不合理的地方,毕竟我仍是菜鸟,但愿能够和大佬们一块儿进步!