在上一篇文章中以简单的方式对Flutter本身提供的演示进行了一个简单的分析,固然那是远远不够。原本打算为你们带来官网上的无限下拉刷新的案例,可是发现这里的有些东西实在是太超前了,做为Flutter入门篇,固然不能这么随意,觉得了让你们都可以学有所得,因此今天给你们带来了本身手撸的一个登陆。java
咱们都知道,一个简单的登陆须要至少须要3步:android
那么咱们的布局也就至少须要3个widget
,为何说至少呢?由于每每布局使用的widget
都是大于操做步骤的。这里跟你们分享个人布局大概有这么几个:git
widget
,能够包裹里面的全部内容。widget
,让全部的内容成纵向排列。widget
很差吗?这里允许我先买个关子~~为何要讲解这个呢?这是由于它是实现了Mataril Design
的一种简单的“脚手架”,有些也叫“支架”,经过这个翻译也就知道了,其实它就是向咱们提供了简单的框架,咱们直接使用它就好了。那么问题来了,咱们可不能够不使用它呢?固然是能够的,可是不建议这样作,由于咱们后面须要使用的不少widget
(好比TextField
)必需要在它的支持下才能运行,否则就会报错了。github
class Scaffold extends StatefulWidget {
/// Creates a visual scaffold for material design widgets.
const Scaffold({ Key key, this.appBar, //横向水平布局,一般显示在顶部(*) this.body, // 内容(*) this.floatingActionButton, //悬浮按钮,就是上图右下角按钮(*) this.floatingActionButtonLocation, //悬浮按钮位置 //悬浮按钮在[floatingActionButtonLocation]出现/消失动画 this.floatingActionButtonAnimator, //在底部呈现一组button,显示于[bottomNavigationBar]之上,[body]之下 this.persistentFooterButtons, //一个垂直面板,显示于左侧,初始处于隐藏状态(*) this.drawer, this.endDrawer, //出现于底部的一系列水平按钮(*) this.bottomNavigationBar, //底部持久化提示框 this.bottomSheet, //内容背景颜色 this.backgroundColor, //弃用,使用[resizeToAvoidBottomInset] this.resizeToAvoidBottomPadding, //从新计算布局空间大小 this.resizeToAvoidBottomInset, //是否显示到底部,默认为true将显示到顶部状态栏 this.primary = true, // this.drawerDragStartBehavior = DragStartBehavior.down, }) : assert(primary != null), assert(drawerDragStartBehavior != null), super(key: key);
复制代码
从这里面,咱们能够看出Scaffold
提供了不少的方式方法,去实现Mataril Design
的布局:bash
通常就用于Scaffold.appBar
,是一个置于屏幕顶部的横向布局,为何是横向呢?能够以下中看出:app
我在它其中的anctions
属性中设置了多个widget
,而后就向这样后面那三就一溜的按顺序排好了。框架
AppBar(
title: Text('Sample Code'),
leading: IconButton(
icon: Icon(Icons.view_quilt),
tooltip: 'Air it',
onPressed: () {},
),
bottom: TabBar(tabs: tabs.map((e) => Tab(text: e)).toList(),controller: _tabController),
actions: <Widget>[
IconButton(
icon: Icon(Icons.playlist_play),
tooltip: 'Air it',
onPressed: () {},
),
IconButton(
icon: Icon(Icons.playlist_add),
tooltip: 'Restitch it',
onPressed: () {},
),
IconButton(
icon: Icon(Icons.playlist_add_check),
tooltip: 'Repair it',
onPressed: () {},
)
],
)
复制代码
对于上述中leading
须要说明一下,通常咱们用它来显示一个按钮去关闭当前页面或者打开一个drawer
。有兴趣的能够去试试~~less
在AppBar
众多的属性中,还有一个是咱们比较经常使用的,那就是bottom
,这个显示于工具栏的下方,注意不是屏幕底部哦!通常使用TabBar
来实现一个页面包含中多个不一样页面的切换。 ide
固然还有其余一些方式方法,这里就很少占用篇幅了,就简单聊聊:工具
title
就是标题drawer
抽屉,通常左侧打开,默认初始隐藏centerTitle
是否标题居中若是想看完整的实现方式,就跟我来吧!
这个属性也是至关重要的,若是咱们想要实现多个,不一样页面的切换,就可使用这个。咦?这个不是说过了么?
BottomNavigationBar与AppBar里面的TabBar是不一样的,一个是用来显示于顶部,一个用来显示与底部
在咱们国内的应用中不多向这样出现能够浮动选择项,因此若是想让你的App不进行浮动的话,可使用里面的一个type
属性。
type: BottomNavigationBarType.fixed,
复制代码
BottomNavigationBarType有两值,就是fixed,还有一个就是shifting,默认是shifting。这样设置以后仍然存在一个问题:就是选中的按钮的字体仍然会比未选中的大一点,有兴趣的能够本身去验证一下。
selectedItemColor: colorRegular, //选中颜色
unselectedItemColor: colorBlack,//未选择颜色
selectedFontSize: 12,//选中字体大小
unselectedFontSize: 12,//未选中字体大小
复制代码
我的以为这个FloatingActionButton
仍是须要说明一下的,毕竟用的时候仍是比较多的。FloatingActionButton
是一个浮动按钮,也就是上面那个带“+”的按钮,这个能够用来添加,分享,或者是导航。能够与Scaffold
中两个属性配合使用
FloatingActionButtonLocation
属性能够移动浮动按钮的位置,有以下几个位置能够移动:
FloatingActionButtonLocation.endDocked //右侧bottomNagivationBar遮盖
FloatingActionButtonLocation.centerDocked //居中bottomNagivationBar上遮盖
FloatingActionButtonLocation.endFloat //bottomNagivationBar上方右侧显示
FloatingActionButtonLocation.centerFloat //bottomNagivationBar上方居中显示
复制代码
本身能够试一试,这里就不一一演示,只演示一下这个centerDocked
FloatingActionButtonAnimator
就是FloatingActionButton
在出现位置FloatingActionButtonLocation
的动画效果~~
须要注意如下几点:
FloatingActionButtonLocation
,那么就须要让每个浮动按钮都有本身且惟一的heroTag
。onPressed
返回了null,那么它将不会对你的触摸进行任何反应,不推荐这样去展现一个无任何响应的浮动按钮。常常在咱们的应用中会使用到信息提示,那么咱们就可使用showSnackBar的方式去显示一个简短的提示,默认显示4s。
class SnackTest extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Hello!'),
));
},
),
)
);
}
}
复制代码
通常咱们会向如上方式处理,可是可能会抛出一个Scaffold.of() called with a context that does not contain a Scaffold.
的异常,也不会显示出snackBar
。
这是由于,Scaffold.of()
所需的context是Scaffold的,并非Scaffold上方的build(BuildContext context)中的,这两个并非一个。
正确的方式是,建立本身的context:
class SnackTest extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Builder(
// Create an inner BuildContext so that the onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: (BuildContext context) {
return Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Hello!'),
));
},
),
);
},
),
);
}
}
复制代码
固然还可使用GlobalKey
的方式:
class ScaffoldTestState extends State<ScaffoldTest> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
void showSnackBar() {
_scaffoldKey.currentState
.showSnackBar(new SnackBar(content: Text("SnackBar is Showing!")));
}
return new Scaffold(
key: _scaffoldKey,
body: Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
showSnackBar(),
));
},
),
)
}
}
复制代码
还有另外一种也能够做为提示,就是bottomSheet:
snackBar
的区别就是,虽然弹出了提示,可是不会自动消失,须要手动下拉才会消失。
class SnackTest extends StatelessWidget{
void showBottomSheet(BuildContext context) {
Scaffold.of(context).showBottomSheet((BuildContext context) {
return new Container(
constraints: BoxConstraints.expand(height: 100),
color: Color(0xFFFF786E),
alignment: Alignment.center,
child: new Text(
"BottomSheet is Showing!",
style: TextStyle(color: Color(0xFFFFFFFF)),
),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Builder(
// Create an inner BuildContext so that the onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: (BuildContext context) {
return Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
showBottomSheet(context);
},
),
);
},
),
);
}
}
复制代码
前面讲了那么多都是为咱们接下来的演示作准备的,那先来看看登陆代码:
class LoginPageState extends State<LoginPage> {
Color colorRegular = Color(0xFFFF786E);
Color colorLight = Color(0xFFFF978F);
Color colorInput = Color(0x40FFFFFF);
Color colorWhite = Colors.white;
TextStyle defaultTextStyle =
TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16);
BorderRadius radius = BorderRadius.all(Radius.circular(21));
void login() {
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: Container(
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [colorLight, colorRegular],
begin: Alignment.topCenter,
end: Alignment.bottomCenter)),
child: Column(
children: <Widget>[
Container (
margin: EdgeInsets.only(top: 110, bottom: 39, left: 24, right: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(21)), color: colorInput),
child: TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 15,vertical: 9),
border: InputBorder.none,
hintText: "输入手机号",
hintStyle: TextStyle(color: Colors.white, fontSize: 16),
labelStyle: TextStyle(color: Colors.black, fontSize: 16)),
maxLines: 1,
cursorColor: colorRegular,
keyboardType: TextInputType.phone,
),
),
Container(
margin: EdgeInsets.only(bottom: 58, left: 24, right: 24),
decoration: BoxDecoration(
borderRadius: radius,
color: colorInput),
child: TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 15,vertical: 9),
border: InputBorder.none,
hintText: "输入密码",
hintStyle: TextStyle(color: Colors.white, fontSize: 16),
labelStyle: TextStyle(color: Colors.black, fontSize: 16)),
maxLines: 1,
cursorColor: colorRegular,
keyboardType: TextInputType.number,
obscureText: true,
),
),
Container(
height: 42, width: 312,
margin: EdgeInsets.only(left: 24, right: 24),
decoration: BoxDecoration (
borderRadius: radius,
color: colorWhite),
child: RaisedButton(onPressed: login,
elevation: 1,
highlightElevation: 1,
textColor: colorRegular,
shape: RoundedRectangleBorder(
borderRadius: radius
),
child: new Text("当即登陆", style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold),
)),
),
Padding(
padding: EdgeInsets.only(top: 10),
child: Text(
"登陆/注册即表明您已赞成《会员协议》",
style: TextStyle(color: Colors.white, fontSize: 13),
),
),
],
),
),
);
}
}
复制代码
在上一章就讲过,若是在整个生命周期中,状态若是改变,那么咱们就是用StatefulWidget
来呈现,而且StatefulWidget
的实现须要两步:一个是须要建立继承StatefulWidget
的类;另外一个就是建立继承State
的类,通常在State
中控制整个状态。因此此处就是如此:
class LoginPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => LoginPageState();
}
class LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: Container(
//省略代码
...
)
);
}
}
复制代码
而且当前登陆界面是没有工具栏的,因此去掉了AppBar
。将全部内容直接写在了body
中。能够看到整个登陆界面的背景是一个渐变,上面浅一点,下面深一点,因此就须要一个容器去包裹整个内容,而且这个容器能够实现背景颜色的渐变的,因此我选用了Container
,由于它是全部容器布局中属性最全面的。
Container({
Key key,
this.alignment,//子布局的排列方式
this.padding,//内部填充
Color color,//背景颜色
Decoration decoration, //用于装饰容器
this.foregroundDecoration,//前景装饰
double width, //容器宽
double height, //容器高
BoxConstraints constraints, //约束
this.margin, //外部填充
this.transform, //对容器进行变换
this.child,
})
复制代码
提示:若是处于body
下的container
不管是否设置宽高,它将都会扑满全屏。
那么最外层的渐变咱们就是使用BoxDecoration
:
const BoxDecoration({ this.color, this.image, 图片 this.border, //边框 this.borderRadius, //圆角 this.boxShadow, //阴影 this.gradient, //渐变 this.backgroundBlendMode, //背景模式,默认BlendMode.srcOver this.shape = BoxShape.rectangle, //形状 }) : assert(shape != null), assert( backgroundBlendMode == null || color != null || gradient != null, 'backgroundBlendMode applies to BoxDecoration\'s background color or ' 'gradient, but no color or gradient was provided.' );
复制代码
提示:在对形状的处理中,如下是能够互换的:
因此从上能够完成咱们的渐变:
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [colorLight, colorRegular],
begin: Alignment.topCenter,
end: Alignment.bottomCenter)
)
复制代码
实现了渐变的过程,那么就是输入框,能够从设计上来讲,这些内容都是纵向排列的,因此内容使用了布局Column
,用于纵向布局,固然相对的横向布局Row
。
Column({
Key key,
//主轴排列方式,这里的主轴就是纵向,实际就是纵向的布局方式
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
//Column在主轴(纵向)占有的控件,默认尽量大
MainAxisSize mainAxisSize = MainAxisSize.max,
//交叉轴排列方式,那么就是横向
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
//横向子widget的布局顺序
TextDirection textDirection,
//交叉轴的布局对齐方向
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
复制代码
在Column
中包含了三个Container
,前两个中是输入布局TextField
,最后一个是RaisedButton
。这里回答在文章开始开始的时候提出的问题:为何要用Container
去包裹TextField
?
全部须要使用Container
去完成这样的样式装饰。
TextField
应该是咱们比较经常使用的widget了:
TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 15,vertical: 9),
border: InputBorder.none,
hintText: "输入手机号",
hintStyle: TextStyle(color: Colors.white, fontSize: 16),
labelStyle: TextStyle(color: Colors.black, fontSize: 16)
),
maxLines: 1,
cursorColor: colorRegular,
keyboardType: TextInputType.phone,
),
复制代码
这里只是使用可decoration
,对TextField
装饰,好比其中的contentPadding
,对内容留白填补。 cursorColor
光标颜色,输入类型keyboardType
,这里是手机号类型。此外还有不少的属性,这里就不一一赘述,能够自行到官网去查看。
最后被container
包裹是的RaisedButton
:
RaisedButton(
onPressed: login,
elevation: 1,
highlightElevation: 1,
textColor: colorRegular,
shape: RoundedRectangleBorder(
borderRadius: radius
),
child: new Text("当即登陆", style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold),
))
复制代码
TextField
进行输入的时候,会发现整个布局会被顶上去了,这是为何呢?。答:这是由于Scaffold
会填充整个可用空间,当软键盘从Scaffold
布局中出现,那么在这种状况下,可用空间变少Scaffold
就会从新计算大小,这也就是为何Scaffold
会将咱们的布局所有上移的根本缘由,为了不这种状况,可使用resizeToAvoidBottomInset
并将其 置为false
就能够了。
debug
标签?答:将MaterialApp中的debugShowCheckedModeBanner
置为false
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Color(0xFFFF786E),
primaryColorLight: Color(0xFFFF978F),
accentColor: Color(0xFFFFFFFF)
),
home: LoginPage(),
debugShowCheckedModeBanner: false,
);
}
}
复制代码
runApp(new MyApp());
if (Platform.isAndroid) {
// 如下两行 设置android状态栏为透明的沉浸。
//写在组件渲染以后,是为了在渲染后进行set赋值,
//覆盖状态栏,写在渲染以前MaterialApp组件会覆盖掉这个值。
SystemUiOverlayStyle systemUiOverlayStyle =
SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
复制代码
最后给你们推荐一本Flutter书,详细介绍了Flutter的使用方式方法,都提供了演示案例:Flutter实战: