如果路上有坑,就要毫不犹豫的跳下去

登陆页是一个软件的门面。一个完整的登陆页包含账号密码登陆、验证码登陆、注册及忘记密码四个功能,下面从框架开始一步步完成。

踩坑记录:

  1. 背景图由于键盘弹起导致图片变形
  2. 输入框由于键盘弹起上移,虽然避免了键盘遮挡,但是效果不好

如果你只想看代码,请到页面最下方,有完整代码

搭建环境

1. 封装网络请求库

登陆功能需要实现网络请求的功能,引入dio库来进行网络请求类的封装,需要学习的可以参考pub官网的教程,有中文版哦

dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等…

为了方便使用,Dio提供了一些其它的Restful API, 这些API都是request的别名。

2. 导入持久存储库

shared_preferences 为简单数据提供一个持久的存储。数据被异步地持久化到磁盘。需要注意的一点是,两个平台都不能保证写操作在返回后被持久化到磁盘上,所以这个插件不能用于存储关键数据。

这里主要用于缓存一下token

编写widget

如何搭建程序框架就不在这里介绍了,下面主要讲解登陆页面的搭建

给页面加个背景图

class LoginPage extends StatefulWidget {static final String sName = "login";@override_LoginPageState createState() => _LoginPageState();
}class _LoginPageState extends State<LoginPage> {@overridevoid initState() {super.initState();}@overrideWidget build(BuildContext context) {return new Container(decoration: BoxDecoration(image: DecorationImage(image: AssetImage('static/images/login_back.jpeg'),fit: BoxFit.cover,),),);}
}

最外围的Container在默认情况下会铺满整个屏幕,使用BoxDecoration对container进行装饰。

这里有个坑需要注意一下,一般情况下我们都会用Scaffold 来包装整个页面,因为这个组件提供了一系列非常方便的属性来配置整个页面,但如果我们想要一个背景图,就必须先写一个Container,因为如果使用Scaffold ,就会在键盘弹出的时候导致页面重绘,背景图会受到挤压而变形。

编写页面主体

这时候就可以使用Scaffold模块来写页面了。

@overrideWidget build(BuildContext context) {return new Container(decoration: BoxDecoration(image: DecorationImage(image: AssetImage('static/images/login_back.jpeg'),fit: BoxFit.cover,),),child: Scaffold(resizeToAvoidBottomPadding: false, // 这里需要注意一下backgroundColor: Color.fromARGB(150, 255, 255, 255),body: Container(padding: EdgeInsets.all(40),child: getLoginComp(),),),);}

这里也有一个坑,Scaffold 在键盘弹出的时候,会由于底部的挤压而重绘页面,我们已经避免了背景图的变形,但是 Scaffold 模块内部的组件会在键盘弹出的时候整体向上偏移,这个特性可以解决输入框被键盘遮挡的问题,但是有些人可能不需要这一特性,可以将resizeToAvoidBottomPadding 属性设置为false。

编写用户名及密码输入框

 // 用户名输入框TextField(controller: _usernameController,decoration: InputDecoration(// labelText: "用户名",hintText: "手机号",prefixIcon: Icon(Icons.person), // 前置contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),border: // 椭圆形输入外框OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),),keyboardType: TextInputType.number, // 弹起的键盘类型inputFormatters: [ // 输入校验,这里只允许输入数字WhitelistingTextInputFormatter(RegExp("[0-9]")),]),/// 密码输入框TextField(controller: _passwordController,decoration: InputDecoration(// labelText: "密码",hintText: "您的登录密码",prefixIcon: Icon(Icons.lock),contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),),obscureText: true,);

验证码输入框

因为我想把验证码按钮放到输入框里,所以使用stack组件进行包裹

Stack(children: <Widget>[TextField(// autofocus: true,keyboardType: TextInputType.number,controller: _verCodeController,decoration: InputDecoration(prefixIcon: Icon(Icons.lock),contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),border:OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),),),Positioned(right: 5,child: FlatButton(disabledColor: Colors.grey[400],color: Colors.blue,// highlightColor: Colors.transparent,colorBrightness: Brightness.dark,// splashColor: Colors.grey,child: Text(vercodeDelay == 0 ? "获取验证码" : vercodeDelay.toString() + 's',style: TextStyle(fontSize: 12),),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),onPressed: vercodeDelay == 0? () {onGetVerification();}: null,),)],);

完整的登陆模块页面如下:

/// 登陆模块getLoginComp() {return Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text('Welcome',style: TextStyle(fontSize: 32,),),SizedBox(height: 32,),TextField(// autofocus: true,controller: _usernameController,decoration: InputDecoration(// labelText: "用户名",hintText: "手机号",prefixIcon: Icon(Icons.person),contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),border:OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),),keyboardType: TextInputType.number,inputFormatters: [WhitelistingTextInputFormatter(RegExp("[0-9]")),]),SizedBox(height: 12,),loginMode == 'login' ? getPasswordComp() : getVerificationComp(),SizedBox(height: 32,),SizedBox(width: double.infinity,height: 40,child: FlatButton(color: Colors.blue,// highlightColor: Colors.transparent,colorBrightness: Brightness.dark,// splashColor: Colors.grey,child: Text("登陆",style: TextStyle(fontSize: 20),),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),onPressed: () {onLogin();},),),Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[FlatButton(color: Colors.transparent,child: Text(loginMode == 'login' ? "验证码登陆" : "账号密码登陆",style: TextStyle(fontSize: 16, color: Colors.blue),),onPressed: () {onChangeLoginMode();},),FlatButton(color: Colors.transparent,child: Text("忘记密码",style: TextStyle(fontSize: 16, color: Colors.blue),),onPressed: () {},),],)],);}

后续补充了账号登陆和验证码登陆的切换模式,完整代码如下:

class LoginPage extends StatefulWidget {static final String sName = "login";@override_LoginPageState createState() => _LoginPageState();
}class _LoginPageState extends State<LoginPage> {/// 定义登陆模式:账号密码登陆或者String loginMode = 'login';/// 定义验证码按钮被点击后的延时int vercodeDelay = 0;Timer _timer;TextEditingController _usernameController = TextEditingController();TextEditingController _passwordController = TextEditingController();TextEditingController _verCodeController = TextEditingController();@overridevoid initState() {super.initState();}@overrideWidget build(BuildContext context) {return new Container(decoration: BoxDecoration(image: DecorationImage(image: AssetImage('static/images/login_back.jpeg'),fit: BoxFit.cover,),),child: Scaffold(resizeToAvoidBottomPadding: false, // 就是这里backgroundColor: Color.fromARGB(150, 255, 255, 255),body: Container(padding: EdgeInsets.all(40),child: getLoginComp(),),),);}getPasswordComp() {return TextField(controller: _passwordController,decoration: InputDecoration(// labelText: "密码",hintText: "您的登录密码",prefixIcon: Icon(Icons.lock),contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),),obscureText: true,);}/// 登陆模块getLoginComp() {return Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text('Welcome',style: TextStyle(fontSize: 32,),),SizedBox(height: 32,),TextField(// autofocus: true,controller: _usernameController,decoration: InputDecoration(// labelText: "用户名",hintText: "手机号",prefixIcon: Icon(Icons.person),contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),border:OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),),keyboardType: TextInputType.number,inputFormatters: [WhitelistingTextInputFormatter(RegExp("[0-9]")),]),SizedBox(height: 12,),loginMode == 'login' ? getPasswordComp() : getVerificationComp(),SizedBox(height: 32,),SizedBox(width: double.infinity,height: 40,child: FlatButton(color: Colors.blue,// highlightColor: Colors.transparent,colorBrightness: Brightness.dark,// splashColor: Colors.grey,child: Text("登陆",style: TextStyle(fontSize: 20),),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),onPressed: () {onLogin();},),),Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[FlatButton(color: Colors.transparent,child: Text(loginMode == 'login' ? "验证码登陆" : "账号密码登陆",style: TextStyle(fontSize: 16, color: Colors.blue),),onPressed: () {onChangeLoginMode();},),FlatButton(color: Colors.transparent,child: Text("忘记密码",style: TextStyle(fontSize: 16, color: Colors.blue),),onPressed: () {},),],)],);}/// 验证码模块getVerificationComp() {return Stack(children: <Widget>[TextField(// autofocus: true,keyboardType: TextInputType.number,controller: _verCodeController,decoration: InputDecoration(prefixIcon: Icon(Icons.lock),contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),border:OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),),),Positioned(right: 5,child: FlatButton(disabledColor: Colors.grey[400],color: Colors.blue,// highlightColor: Colors.transparent,colorBrightness: Brightness.dark,// splashColor: Colors.grey,child: Text(vercodeDelay == 0 ? "获取验证码" : vercodeDelay.toString() + 's',style: TextStyle(fontSize: 12),),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),onPressed: vercodeDelay == 0? () {onGetVerification();}: null,),)],);}/// 获取验证码onGetVerification() async {if (vercodeDelay != 0) {return;}String phone = '18515597305';// String phone = _usernameController.text;if (!CommonUtils.isTelphone(phone)) {return;}setState(() {vercodeDelay = 60;startCountdownTimer();});// var res = await NetUtils.getVercode(phone);}/// 变更登陆模式onChangeLoginMode() {if (loginMode == 'login') {this.setState(() {loginMode = 'Verification';});} else {this.setState(() {loginMode = 'login';});}}/// 账号密码登陆onLogin() async {String phone = _usernameController.text;String password = _passwordController.text;if (phone == '' || phone.length != 11) {return;}if (password == '' || password.length < 6) {return;}if (loginMode == 'login') {Map param = {'phone': phone,'password': password,};Map res = await NetUtils.login(param);if (res['code'] == 200) {Navigator.of(context).pushReplacementNamed("home");}return;}var res = await NetUtils.vercodeLogin(password);if (res['code'] == 200) {Navigator.of(context).pushReplacementNamed("home");}}/// 倒计时void startCountdownTimer() {const oneSec = const Duration(seconds: 1);var callback = (timer) => {setState(() {if (vercodeDelay < 1) {_timer.cancel();} else {vercodeDelay = vercodeDelay - 1;}})};_timer = Timer.periodic(oneSec, callback);}@overridevoid dispose() {super.dispose();if (_timer != null) {_timer.cancel();}}
}

flutter:一个完整的登陆页相关推荐

  1. Flutter 构建一个完整的聊天应用程序

    在本教程中,我将向您展示如何使用 Flutter 构建一个完整的聊天应用程序.对于这一部分,我们将创建应用程序的 UI 原型,然后我将向您展示如何使用 firebase 创建后端服务并创建聊天系统. ...

  2. php生成静态翻页,PHP高手,我刚刚学PHP,在生成静态分页遇到了点有关问题,就是翻页的有关问题,希望可以給出一个漂亮完整的翻页代码,多谢...

    各位大哥PHP高手,小弟我刚刚学PHP,在生成静态分页遇到了点问题,就是翻页的问题,希望可以給出一个漂亮完整的翻页代码,谢谢! php生成静态html分页实现方法 '; } else { echo'数 ...

  3. 2 一个完整的计算机系统包括,一个完整的计算机系统包括(19页)-原创力文档...

    PAGE 一. 1). 一个完整的计算机系统包括 A). 主机.键盘和显示器 B). 计算机与外部设置 C). 硬件系统和软件系统 D). 系统软件与应用软件 2). 在多媒体计算机系统中,CD-RO ...

  4. vue+node+mongodb 搭建一个完整博客

    Vue + Node + Mongodb 开发一个完整博客流程 前言 前段时间刚把自己的个人网站写完, 于是这段时间因为事情不是太多,便整理了一下,写了个简易版的博客系统 服务端用的是 koa2框架 ...

  5. 在rhas3.0上建立一个完整的邮件系统(内含四部分)修正版 V

    http://www.chinaunix.net 作者:llzqq发表于:2004-02-28 07:39:56 rhas3.0+qmail+mysql+smtp+vpopmail+igenus+qm ...

  6. Python分布式爬虫打造搜索引擎完整版-基于Scrapy、Redis、elasticsearch和django打造一个完整的搜索引擎网站

    Python分布式爬虫打造搜索引擎 基于Scrapy.Redis.elasticsearch和django打造一个完整的搜索引擎网站 https://github.com/mtianyan/Artic ...

  7. WCF技术剖析之三十二:一步步创建一个完整的分布式事务应用

    在完成了对于WCF事务编程(<上篇>.<中篇>.<下篇>)的介绍后,本篇文章将提供一个完整的分布式事务的WCF服务应用,通过本例,读者不仅仅会了解到如何编程实现事务 ...

  8. 如何在终端下截取一个完整长度的网页截图

    与其记笔记或是把看到的内容发送给其他人,我们更经常通过截屏来帮助我们记忆. 但是通常情况下,如果一个网页超出了屏幕高度,我们就得用多张截图去截取其全部内容. 对于 Linux,你将会有一个更好的解决方 ...

  9. dj鲜生-24-含资源-模板操作-注册与登陆页的模板继承

    资源-继承好的模板 https://cloud.189.cn/t/vaYfE3rIVVrm (0928-继承好了的前端) 注册页面 去掉重复的内容 改造好的 完整的 同理,登陆页的模板继承 这样子写

最新文章

  1. 剑指offer_第8题_跳台阶
  2. Java 实现第三方 QQ 账号登录
  3. python如何读写文件-python文件的写入和读取
  4. USEFORM,USERES详解
  5. java handler null_java – 在调用之前,如何确保另一个Thread的Handler不为null?
  6. GitLab 在多分支中的一个push
  7. 湖北工业大学查分_湖北工业大学成人高考低于分数线没考上怎么办?
  8. python调用海康威视的摄像头_Python调用海康威视网络相机之——python读取相机rtsp码流显示画面...
  9. 计算机专业英语(一)学习方法
  10. FPGA之旅设计99例之第十八例----OV5640摄像头SCCB时序
  11. standard-version(发版与 Changelog 自动化)
  12. 最新计算机专业毕业设计论文选题源码演示录像下载(开题报告任务书PPT毕业答辩模板 jsp70786体育馆售票门票系统 双数据库 mysql版
  13. 餐厅扫码点餐怎么弄_分享扫码点餐小程序开发制作方法
  14. 苹果手机点击输入框input 页面放大 超出屏幕问题
  15. Linux创建磁盘并分区命令
  16. 算法导论 直接寻址表
  17. 程序猿的世界~~~~
  18. 基于VU9P+C6678 的 4 路 FMC 接口基带信号处理板(支持 8 路 1G 瞬时带宽 AD+DA)
  19. 使用计算机搭建防火墙,电脑防火墙在哪里设置?电脑防火墙设置方法介绍
  20. readelf指令使用

热门文章

  1. mini-ui 中的message弹出框中点击确定与取消之后使用回调函数的方法
  2. 无密码,删除压缩包密码?
  3. 线程池参数到底要怎么配?这可能是最好的答案
  4. 2021年七台河高考成绩查询,2021年七台河高考状元是谁分数多少分,历年七台河高考状元名单...
  5. 微信小程序插件引入,地铁图插件、地图选点插件、地图选点插件
  6. 快到我的碗里来!瑞典艺术家创作人体美食
  7. 【Libtorch部署】pkl 转 pt
  8. 运输问题(最小费用流)
  9. 实战项目—使用Python,SqlServer,HTML实现简单登录模块
  10. 20个令人惊叹的深度学习应用(Demo+Paper+Code)