技术无止境,只怕不学习啊,Flutter 我们开始吧
先上图来看一下要实现的效果:
页面结构
1.标题
2.手机号输入框
3.验证码输入框
4.获取验证码
5.60s倒计时
6.登录按钮
7.跳转到主页
下面直接上代码说明
/// (`InkWell`)可用时使用的字体样式。
final TextStyle _availableStyle = TextStyle(
fontSize: 16.0,
color: const Color(0xFF00CACE),
);
/// (`InkWell`)不可用时使用的样式。
final TextStyle _unavailableStyle = TextStyle(
fontSize: 16.0,
color: const Color(0xFFCCCCCC),
);
class LogInPage extends StatefulWidget {
/// 倒计时的秒数,默认60秒。
final int countdown;
const LogInPage({Key key, this.countdown: 60}) : super(key: key);
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return LogInPageState();
}
} //
class LogInPageState extends State<LogInPage> {
String _verifyStr = '获取验证码';
/// 倒计时的计时器。
Timer _timer;
/// 当前倒计时的秒数。
int _seconds;
/// 按钮文案
String _buttonName = "登录";
/// 标题
String title = "测试";
/// 当前(`InkWell`)的字体样式。
TextStyle inkWellStyle = _availableStyle;
TextEditingController _userController = new TextEditingController(); // 手机号
TextEditingController _passWordController =
new TextEditingController(); // 验证码
@override
void initState() {
super.initState();
_seconds = widget.countdown;
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: new ListView(
children: <Widget>[
new Container(
color: Colors.white, // 背景色
padding: EdgeInsets.only(top: 78, left: 8, right: 17),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center, // 对齐方式
children: <Widget>[
_setTitle(title), // 设置标题
_setAccount(), // 账号
_setCode(), // 验证码
_raisedButton() // 按钮
], // 子集数组
),
),
], // listview 子集列表
), // 列表
); //控制整个布局添加标题 策划 顶部底部按钮等
}
/// 标题
Widget _setTitle(String title) {
return new Text(
title,
style: TextStyle(
fontSize: 35, // 字体大小
color: Colors.black // 字体颜色
),
);
}
/// 手机号输入框
Widget _setAccount() {
return new Container(
margin: EdgeInsets.only(top: 15),
child: _createTextField(context, "请输入手机号码", 1, false, _userController),
);
} //
/// 验证码输入框
Widget _setCode() {
return new Stack(
children: <Widget>[
_createTextField(context, "短信验证码", 2, true, _passWordController),
new Container(
height: 54.0,
child: new Align(
alignment: FractionalOffset.centerRight, // 右侧对齐
child: GestureDetector(
// 手势交互
onTap: (){
setState(() {
String user = _userController.text.toString();
if (user.isEmpty|!RegularMatch.isChinaPhoneLegal(user)) {
Fluttertoast.showToast(
msg: "请输入正确的电话号码!",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP,
fontSize: 12,
timeInSecForIos: 1);
return;
}
_startTimer(); // 倒计时
});
},
child: new Visibility(
child: new Text(
_verifyStr,
style: TextStyle(
color: Colors.blue,
fontSize: 16,
),
)),
),
), // 设置 布局的高宽适应
),
], // 子集列表
); // 叠加布局
} //
/// 登录按钮
Widget _raisedButton() {
return new Container(
width: double.infinity,
height: 45,
margin: EdgeInsets.only(left: 19, top: 35),
child: new RaisedButton(
onPressed: () {
// 点击事件
String user = _userController.text;
String pw = _passWordController.text;
// RegExp exp = RegExp(
// r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\d{8}$');
// 手机号校验
if (null == user || user.length < 11 || !RegularMatch.isChinaPhoneLegal(user)) {
Fluttertoast.showToast(
msg: "请输入正确的电话号码!",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP);
return;
}
// 验证码校验
if (null == pw || pw.length < 4) {
Fluttertoast.showToast(
msg: "请输入正确的验证码!",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP);
return;
}
// 路由页面跳转
Navigator.pushNamed(
context, '/Home'); //使用的是“命名导航路由”,具体去哪个界面,看main.dart 对应routeName('/Home')的界面
},
color: Color(0xff017EFF), // 按钮颜色
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(5)),
), // 弧度
child: new Text(
_buttonName,
style: TextStyle(
color: Colors.white,
fontSize: 17,
),
),
),
);
} //
/// 输入框
Widget _createTextField(BuildContext context, String hintText, int type,
autovalidate, TextEditingController _controller) {
return new Theme(
data:
new ThemeData(primaryColor: Colors.red, hintColor: Color(0xffa8afc3)),
child: new ConstrainedBox(
constraints: BoxConstraints(
minHeight: 54, // 设置高度
),
child: new Padding(
padding: EdgeInsets.only(top: 19, left: 15),
child: new TextFormField(
obscureText: false, // 是否是密码
controller: _controller, // 设置输入框
keyboardType: TextInputType.number, // 唤起键盘输入方式
inputFormatters: <TextInputFormatter>[
LengthLimitingTextInputFormatter(1 == type ? 11 : 4)
], // 输入长度
autovalidate: autovalidate, // 自我验证
validator: (value) {
return value.length < 4 && value.length > 0 ? "验证码长度不够4位" : null;
}, // 输入的长度
decoration: new InputDecoration(
contentPadding: EdgeInsets.all(10.0),
hintText: hintText, // 提示文案
hintStyle: TextStyle(
color: Color(0xffa8afc3), // 字体颜色
), // 提示文案的样式设置
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue)), // 边框颜色
border: UnderlineInputBorder(), // 设置装饰形状
),
),
),
),
);
} //
/// 启动倒计时的计时器。
void _startTimer() {
if(_timer!=null){
if(_timer.isActive){
return;
}
}
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
if (_seconds == 0) {
_cancelTimer();
_seconds = widget.countdown;
inkWellStyle = _availableStyle;
setState(() {});
return;
}
_seconds--;
_verifyStr = '${_seconds}s';
setState(() {});
if (_seconds == 0) {
_verifyStr = '重新发送';
}
});
} //
/// 取消倒计时的计时器。
void _cancelTimer() {
// 计时器(`Timer`)组件的取消(`cancel`)方法,取消计时器。
_timer?.cancel();
}
} //
手机号校验
class RegularMatch{
static const String PHONE_REG = r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\d{8}$';
///校验用户手机号码
static bool isChinaPhoneLegal(String str) {
return new RegExp(PHONE_REG).hasMatch(str);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
routes: {
'/': (BuildContext context) => new LogInPage(),
'/Home': (BuildContext context) => new MyHomePage(title: '主页',),
},
// home: LogInPage(),
);
}
}
源码下载