闪屏

先看下闪屏动画的代码:
import 'package:flutter/material.dart';
import 'login_screen.dart';
class SplashScreen extends StatefulWidget {
@override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 3000));
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
/* 动画事件监听器
监听动画的执行状态,这里监听动画结束的状态,如果结束则执行页面跳转
*/
_animation.addStatusListener((status){
if (status == AnimationStatus.completed) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => LoginView()),
(route) => route==null
);
}
});
// 播放动画
_controller.forward();
}
@override
Widget build(BuildContext context) {
return FadeTransition( // 透明动画组件
opacity: _animation, // 执行动画
child: Image.network(
'http://img.ph.126.net/L-kWzHFAymXFUvgauDtB-g==/2596888135149416738.jpg',
scale: 2.0, // 缩放
fit: BoxFit.cover, // 充满容器
),
);
}
}
AnimationContrller是Animation的一个子类,可以用它来控制动画,比如执行时间。
设置好动画,就设置一个动画事件的监听器animation.addStatusListener,它可以监听到动画的执行状态,这里监听到动画结束,执行页面跳转动作。
关于图片的设置,以及路由的跳转,在后面都有详细的解释。
登录界面

这是一个很简单的登录界面,先看下页面里主要都有什么。
- 图片logo
- 上下两个输入框
- 一个登录按钮
这边了解过Flutter的小伙伴们,看到了这个布局一下子就能够想得到Column,下面看主体代码
import 'package:flutter/material.dart';
import 'home_page.dart';
class LoginView extends StatefulWidget {
@override
_LoginViewState createState() => _LoginViewState();
}
class _LoginViewState extends State<LoginView> {
var _photoController = TextEditingController();
var _passController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: Image.asset('images/logo.jpg'),
),
AccountTextField(),
PasswordTextField(),
LoginButton(),
],
),
),
);
}
// 账号TextField
Widget AccountTextField() {
return Padding(
padding: const EdgeInsets.fromLTRB(15.0, 30.0, 15.0, 0.0),
child: TextField(
controller: _photoController,
onChanged: (text) { // 内容变化
print('输入了$text');
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone_iphone),
fillColor: Colors.white70,
filled: true,
labelText: '请输入手机号'
),
)
);
}
// 密码TextField
Widget PasswordTextField() {
return Padding(
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 0.0),
child: TextField(
controller: _passController,
decoration: InputDecoration(
prefixIcon: Icon(Icons.email),
fillColor: Colors.white70,
filled: true,
labelText: '请输入密码'
),
obscureText: true,
)
);
}
// 登录按钮
Widget LoginButton() {
return GestureDetector(
child: Container(
margin: const EdgeInsets.fromLTRB(0.0, 30.0, 0.0, 0.0),
width: MediaQuery.of(context).size.width - 30.0,
height: 50.0,
child: Card(
color: Colors.lightBlue,
elevation: 6.0,
child: Center(
child: Text('登录', style: TextStyle(fontSize: 18.0, color: Colors.white)),
)
)
),
onTap: (){
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => HomePage()),
(route) => route==null
);
},
);
}
}
说明一下布局:
- 主体引入一个
Column Widget - 第一个子
Widget是一个logo图片,它被Padding Widget包裹着,我们可以通过Padding的padding属性,轻松的控制这个Widget位置和边距 - 第二个和第三个
子Widget是一个TextField Widget,和上面的图片一样,通过Padding Widget包裹着,很容易控制它们的边距 - 第四个子
Widget用的是一个Container,然后里面通过GestureDetector添加了点击事件,其实这里的登录按钮,完全可以通过button性质的Widget来做,这里只是想记录一下,给控件添加手势的功能
下面简单介绍一下几个简单的Widget:
图片Widget
本地图片通过 pubspec.yaml 文件(位于项目根目录),来识别应用程序所用的图片
在根目录下新建一个images文件夹,存放图片使用
assets:
- images/logo.png
- images/background.png
代码中可以直接引用,和iOS开发中的使用类似:Image.asset('images/logo.jpg')
网络图片的加载,看一下加载网络图片的方法Image.network('图片链接'),也很简单。
TextField Widget
最简单的输入框TextField(),只要这一句话,输入框其实就有了

通过项目里的代码可以看到,TextField里有个controller属性,通过这个来获取TextField的值:controller.text。
做iOS的小伙伴们都知道iOS中的TextField属性中有个placeholder属性,那么这里可以通过decoration里的labelText设置这个值。
一般的密码输入框,我们都是隐式显示,也就是▪️▪️▪️▪️来表示。obscureTexts设置为true即可。
还可以在左侧添加一个icon,代码中也可以看到用法。
有兴趣的小伙伴可以通过源码里的属性值,来了解这个Widget。

本人项目中使用了很多圆角矩形的边框,这里有点坑,下面看下代码:
import 'package:flutter/material.dart';
class TextFieldView extends StatelessWidget {
Widget borderTextField() {
return TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: Colors.red, width: 10.0, style: BorderStyle.solid)
)
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('TextField')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: borderTextField(),
),
),
);
}
}
效果图:

可能,细心的小伙伴们已经发现了问题
borderSide: BorderSide(color: Colors.red, width: 10.0, style: BorderStyle.solid),这句代码对边框的设置毫无作用,这里的输入框,并不是我们想要的
好在坑已被前人填满,:边框颜色解决方案
所以正确的代码姿势应该是:
Widget borderTextField() {
return Theme(
data: ThemeData(primaryColor: Colors.red, hintColor: Colors.blue),
child: TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
// 下面方法无效
// borderSide: BorderSide(color: Colors.red, width: 10.0, style: BorderStyle.solid)
)
),
),
);
}
效果图:

再者就是边框的粗细改变,暂时没有找到合适的方法,但是可以通过重构的方式,请看代码:
import 'package:flutter/material.dart';
class TextFieldView extends StatelessWidget {
Widget borderTextField() {
return Container(
padding: const EdgeInsets.all(5.0),
height: 40.0,
decoration: BoxDecoration(
color: Colors.white70,
border: Border.all(color: Colors.yellow, width: 5.0),
borderRadius: BorderRadius.circular(8.0)
),
child: TextFormField(
decoration: InputDecoration.collapsed(hintText: '请输入内容'),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('TextField')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: borderTextField(),
),
),
);
}
}
效果图:

页面跳转及路由
Navigator
Navigator继承StatefulWidget,它也是小组件,有很多相关静态函数,可以帮助我们达到页面跳转和数据交互的功能:
- push 将设置的route信息推送到Navigator上,实现页面跳转
- of 主要是获取Navigator最近实力的好状态
- pop 返回到上个页面
- canPop 判断是否可以导航到新页面
- popAndPushNamed 指定一个路由路径,并导航到新页面
- popUntil 反复执行pop知道该函数的参数predicate返回true为止
- pushAndRemoveUntil 将给定路由推送到Navigator,删除先前的路由,直到该函数的参数predicate返回true为止
- pushNamed 将命名路由推送到Navigator
- pushNamedAndRemoveUntil 将命名路由推送到Navigator,删除先前的路由,直到该函数的参数predicate返回true为止。
- pushReplacement 路由替换。
- pushReplacementNamed 这个也是替换路由操作。推送一个命名路由到Navigator,新路由完成动画之后处理上一个路由。
- removeRoute 从Navigator中删除路由,同时执行Route.dispose操作。
- removeRouteBelow 从Navigator中删除路由,同时执行Route.dispose操作,要替换的路由是传入参数anchorRoute里面的路由。
- replace 将Navigator中的路由替换成一个新路由。
- replaceRouteBelow 将Navigator中的路由替换成一个新路由,要替换的路由是是传入参数anchorRoute里面的路由。
路由的操作方式
使用Navigator.push实现发送路由,Navigator.pop返回上一个页面。
push函数的有两个:上下文context、Route。这里使用的是MaterialPageRoute,该类必须要传入一个闭包函数WidgetBuilder,参数是 BuildContext对象,我们使用的是匿名函数的形式,加上箭头符号:builder: (context) => HomePage()