效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w8f2YKnP-1630141808009)(https://user-gold-cdn.xitu.io/2019/2/9/168d1892b5ed3872?imageView2/0/w/1280/h/960/ignore-error/1)]
下面用一个库来加载和缓存网络图像,也可以与占位符和错误小部件一起使用,在pubspec.yaml 添加依赖cached_network_image: ^0.4.1+1 ,在Dart 文件导入这个库import 'package:cached_network_image/cached_network_image.dart';
//图片
class ImageWidget extends StatelessWidget{
String image_url;
ImageWidget(this.image_url);
@override
Widget build(BuildContext context){
return new CachedNetworkImage(
imageUrl: image_url,
//占位符
placeholder: new CircularProgressIndicator(),
//加载错误时显示的图片
errorWidget: new Icon(Icons.error),
//宽高
width:200,
height: 200,
);
}
}
当图片还没加载出来的时候会显示占位符,当如果加载出错会显示errorWidget 的图片。
7.3.声明分辨率相关的图片
另外Flutter可以为当前设备添加合适其分辨率的图像,其实对于Android原生来说,就是在不同分辨率目录下放置不同分辨率的图片,只不过flutter并不是创建drawable-xxdpi 文件,而是创建以下文件夹:
.../logo.png
.../Mx/logo.png
.../Nx/logo.png
其中M和N是数字标识符,对应于其中包含的图像分辨率,它们指定不同素设备像比例的图片,主资源默认对应于1.0倍的分辨率图片。看下面例子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vsndZ2Na-1630141808011)(https://user-gold-cdn.xitu.io/2019/3/13/169772a5f74137f2?imageView2/0/w/1280/h/960/ignore-error/1)]
在设备像素比率为1.8的设备上,images/2.0x/logo.png 将被选择。对于2.7的设备像素比率,images/3.0x/logo.png 将被选择。如果未在Image控件上指定渲染图像的宽度和高度,以便它将占用与主资源相同的屏幕空间量(并不是相同的物理像素),只是分辨率更高。 也就是说,如果images/logo.png 是72px乘72px,那么images/3.0x/logo.png 应该是216px乘216px; 但如果未指定宽度和高度,它们都将渲染为72像素×72像素(以逻辑像素为单位)。pubspec.yaml 中asset部分中的每一项都应与实际文件相对应,但主资源项除外。当主资源缺少某个资源时,会按分辨率从低到的顺序去选择,也就是说1.0x中没有的话会在2.0x中找,2.0x中还没有的话就在3.0x中找。
return Image(
// 系统会根据分辨率自动选择不同大小的图片
image: AssetImage('images/logo.png'),
// ...
),
8.FlatButton
Flutter 预先定义了一些按钮控件,如FlatButton ,RaisedButton ,OutlineButton ,IconButton 。
- FlatButton:扁平化按钮,继承自
MaterialButton - RaisedButton:凸起按钮,继承自
MaterialButton - OutlineButton:带边框按钮,继承自
MaterialButton - IconButton:图标按钮,继承自
StatelessWidget
下面看看FlatButton ,其他的只是样式稍微不一样,大致用法一样。
//按钮
class FlatButtonWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return FlatButton(
onPressed: (){
Fluttertoast.showToast(
msg:'你点击了FlatButton',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
);
},
child: Text('FlatButton'),
color: Colors.blue,//按钮背景色
textColor: Colors.white,//文字的颜色
onHighlightChanged: (bool b){//水波纹变化回调
},
disabledColor: Colors.black,//按钮禁用时的显示的颜色
disabledTextColor: Colors.black38,//按钮被禁用的时候文字显示的颜色
splashColor: Colors.white,//水波纹的颜色
);
}
}
上面也设置了一些属性,效果图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvclrBLS-1630141808012)(https://user-gold-cdn.xitu.io/2019/2/9/168d269b26914f0a?imageslim)]
四、Flutter布局
Flutter 中拥有30多种预定义的布局widget ,常用的有Container 、Padding 、Center 、Flex 、Row 、Colum 、ListView 、GridView 。用一个表格列出它们的特性和使用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YrCSfKSs-1630141808013)(https://user-gold-cdn.xitu.io/2019/2/9/168d2884d214d7c9?imageView2/0/w/1280/h/960/ignore-error/1)]
下面一一介绍简单用法:
1.Container
一个拥有绘制、定位、调整大小的widget ,示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tgwj4ebb-1630141808014)(https://user-gold-cdn.xitu.io/2019/2/9/168d298303fdfb1f?imageView2/0/w/1280/h/960/ignore-error/1)]
下面直接上例子:
class MyHomePage extends StatelessWidget {
....
body:new ContainWidget(),
...
}
//Container布局
class ContainWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Container(
child:Text("My name is Knight"),
color: Colors.indigo,
width:200,//宽
height:200,//高
margin:EdgeInsets.fromLTRB(5,5,5,5),//设置外边距
padding:EdgeInsets.all(30),//内边距
);
}
}
下面设置边框,添加圆角:
//Container布局
class ContainWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Container(
....
padding:EdgeInsets.all(30),//内边距
decoration: BoxDecoration(//设置边框
//背景色
color:Colors.redAccent,
//圆角
borderRadius: BorderRadius.circular(6),
),
);
}
}
运行效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MqnUjwi4-1630141808015)(https://user-gold-cdn.xitu.io/2019/2/9/168d2a2863ccd207?imageView2/0/w/1280/h/960/ignore-error/1)]
2.Padding
一个Widget ,会给其子Widget 添加指定的填充,示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MAuPGGG7-1630141808015)(https://user-gold-cdn.xitu.io/2019/2/9/168d2aba57b4994a?imageView2/0/w/1280/h/960/ignore-error/1)]
class MyHomePage extends StatelessWidget {
....
body: new PaddingWidget(),
...
}
//Padding布局
class PaddingWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Padding(
//设置左上右下内边距为4,10,6,8
padding:EdgeInsets.fromLTRB(4, 10, 6, 8),
child: Text('My name is Knight'),
);
}
}
效果图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-loDT9Gqm-1630141808016)(https://user-gold-cdn.xitu.io/2019/2/9/168d2aeb018478b8?imageView2/0/w/1280/h/960/ignore-error/1)]
下面实现Container 嵌套Padding :
//Container嵌套Padding
class ContainPaddWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Container(
width:200,//宽
height:200,//高
child: Padding(
padding:EdgeInsets.fromLTRB(4, 10, 6, 8),
child: Text("My name is Knight"),
),
decoration: BoxDecoration(//设置边框
//背景色
color:Colors.redAccent,
//圆角
borderRadius: BorderRadius.circular(6),
),
);
}
}
效果图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67ymLonG-1630141808016)(https://user-gold-cdn.xitu.io/2019/2/9/168d2b99879c9c29?imageView2/0/w/1280/h/960/ignore-error/1)]
3.Center
将其子widget 居中显示在自身内部的widget ,示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ru5yrxz-1630141808017)(https://user-gold-cdn.xitu.io/2019/2/9/168d2b5c3704cb5d?imageView2/0/w/1280/h/960/ignore-error/1)]
//Center
class CenterWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Container(
width:200,//宽
height:200,//高
child: Center(
child: Text("My name is Knight"),
),
decoration: BoxDecoration(//设置边框
//背景色
color:Colors.redAccent,
//圆角
borderRadius: BorderRadius.circular(6),
),
);
}
}
运行效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uVWGT2US-1630141808018)(https://user-gold-cdn.xitu.io/2019/2/9/168d2bdf82968bee?imageView2/0/w/1280/h/960/ignore-error/1)]
Center 作为Container 的孩子,Text 所以在布局的中间。
4.Stack
可以允许其子Widget 简单的堆叠在一起,层叠布局,示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DN2vr7ia-1630141808018)(https://user-gold-cdn.xitu.io/2019/2/10/168d52df573f7550?imageView2/0/w/1280/h/960/ignore-error/1)]
下面直接上代码,把之前的布局全部用上试试:
class MyHomePage extends StatelessWidget {
....
body:new Center(
child:new StackWidget()
),
...
}
//层叠布局
class StackWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Stack(
children: <Widget>[
new Image.network('https://ws1.sinaimg.cn/large/0065oQSqgy1fze94uew3jj30qo10cdka.jpg',
width:300.0,//宽
height:300.0,//高
),
new Opacity(
opacity: 0.6,//不透明度
child:new Container(
width:100.0,
height:100.0,
color:Colors.redAccent,
),
),
new Opacity(
opacity: 0.6,
child:new Container(
width: 200.0,
height:200.0,
color:Colors.indigo,
),
),
],
);
}
}
运行效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vVBDVcU7-1630141808019)(https://user-gold-cdn.xitu.io/2019/2/10/168d55176174ccb6?imageView2/0/w/1280/h/960/ignore-error/1)]
可以看到控件都按Stack 左上角对齐,叠在一起,下面改一下显示位置:
//层叠布局
class StackWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Stack(
//Aliginment的范围是[-1,1],中心是[0,0].注释有写
//和Android一样,左移的取值是往1取,右移是往-1取
//这里注意,它是取stack里范围最大的布局为基准,下面是以Container为//基准对齐
alignment: new Alignment(-0.6, -0.6),
...
);
}
}
运行效果图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avIJPTkX-1630141808019)(https://user-gold-cdn.xitu.io/2019/2/10/168d554154fdf2d6?imageView2/0/w/1280/h/960/ignore-error/1)]
5.Colum
在垂直方向上排列子Widget ,示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8SODSbyW-1630141808020)(https://user-gold-cdn.xitu.io/2019/2/10/168d5c29016f0ea3?imageView2/0/w/1280/h/960/ignore-error/1)]
直接上代码:
class MyHomePage extends StatelessWidget {
...
body:new ColumnWidget(),
....
}
//Column布局
class ColumnWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
color:Colors.blue,
width: 50,
height: 50,
),
Container(
color:Colors.black,
width:50,
height:50,
),
Container(
color:Colors.green,
width:50,
height:50,
),
],
);
}
}
运行效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FImmzDlF-1630141808020)(https://user-gold-cdn.xitu.io/2019/2/10/168d5d05f1fe25b7?imageView2/0/w/1280/h/960/ignore-error/1)]
下面简单设置一下排列方式属性:
return Column(
//设置垂直方向的对齐方式
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
...
);
运行效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEiBFuT3-1630141808021)(https://user-gold-cdn.xitu.io/2019/2/10/168d5e35aaa14d54?imageView2/0/w/1280/h/960/ignore-error/1)]
垂直方向(主轴上)属性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UIDk2JnU-1630141808021)(https://user-gold-cdn.xitu.io/2019/2/10/168d5e8d49bfad1f?imageView2/0/w/1280/h/960/ignore-error/1)]
- MainAxisAlignment.start这是默认值:垂直方向顶部对齐
- MainAxisAlignment.end:垂直方向底部对齐
- MainAxisAlignment.center:垂直方向居中对齐
- MainAxisAlignment.spaceBetween:垂直方向平分剩余空间
- MainAxisAlignment.spaceAround:放置控件后,剩余空间平分成n份,n是子
widget 的数量,然后把其中一份空间分成2份,放在第一个child的前面,和最后一个child的后面,也就是子widget 的之前之后之间均匀分割空闲的一半空间 - MainAxisAlignment.spaceEvenly:放置控件后,把剩余空间平分n+1份,然后平分所有的空间,在子
widget 之前之后之间均匀的分割空闲的空间
下面列一下水平方向(交叉轴)的属性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E8N6zpcW-1630141808022)(https://user-gold-cdn.xitu.io/2019/2/10/168d5f65b16e12a2?imageView2/0/w/1280/h/960/ignore-error/1)]
- CrossAxisAlignment.center这是默认值,水平居中
- CrossAxisAlignment.end:水平方向右侧对齐
- CrossAxisAlignment.start:水平方向左侧对齐
- CrossAxisAlignment.stretch:水平方向拉伸子
child 填充满布局 - CrossAxisAlignment.baseline:和
textBaseline 一起使用
6.Row
在水平方向上排列子widget 的列表,示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DlCJzNEz-1630141808022)(https://user-gold-cdn.xitu.io/2019/2/10/168d67a90ebb695e?imageView2/0/w/1280/h/960/ignore-error/1)]
直接上代码:
class MyHomePage extends StatelessWidget {
....
body:new RowWidget(),
...
}
//Row
class RowWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Row(
children: <Widget>[
Container(
color:Colors.blue,
width: 50.0,
height:50.0,
),
Container(
color:Colors.black,
width:50.0,
height:50.0,
),
Container(
color:Colors.green,
width:50.0,
height:50.0,
),
],
);
}
}
效果图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4lH5D9mI-1630141808023)(https://user-gold-cdn.xitu.io/2019/2/10/168d68cc378b8c51?imageView2/0/w/1280/h/960/ignore-error/1)]
下面简单设置一些属性,和Column 没多大差别:
return Row(
//把剩余空间平分n+1份,然后平分所有的空间
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
...
);
效果图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpWBAiYR-1630141808023)(https://user-gold-cdn.xitu.io/2019/2/10/168d6922d4702cbc?imageView2/0/w/1280/h/960/ignore-error/1)]
水平方向上(主轴上)属性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zMlKMLzr-1630141808023)(https://user-gold-cdn.xitu.io/2019/2/10/168d69756a563f8f?imageView2/0/w/1280/h/960/ignore-error/1)]
- MainAxisAlignment.start这是默认值,水平方向顶部对齐
- MainAxisAlignment.center:水平方向居中对齐
- MainAxisAlignment.end:水平方向底部对齐
- MainAxisAlignment.spaceBetween:水平方向上平分剩余空间
- MainAxisAlignment.spaceAround:放置控件后,剩余空间平分成n份,n是子
widget 的数量,然后把其中一份空间分成2份,放在第一个child的前面,和最后一个child的后面,也就是子widget 的之前之后之间均匀分割空闲的一半空间 - MainAxisAlignment.spaceEvenly:放置控件后,把剩余空间平分n+1份,然后平分所有的空间,在子
widget 之前之后之间均匀的分割空闲的空间 而交叉轴(垂直方向)的属性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NN82oth6-1630141808024)(https://user-gold-cdn.xitu.io/2019/2/10/168d69dc564d0c27?imageView2/0/w/1280/h/960/ignore-error/1)]
- CrossAxisAlignment.center这是默认,垂直居中
- CrossAxisAlignment.end:垂直方向右侧对齐
- CrossAxisAlignment.start:垂直方向左侧对齐
- CrossAxisAlignment.stretch:垂直方向拉伸子
child 填充满布局 - CrossAxisAlignment.baseline:和
textBaseline 一起使用
7.Expanded
Expanded 组件可以使Row 、Column 、Fiex 等子组件在其主轴上方向展开并填充可用的空间,这里注意:Expanded 组件必须用在Row 、Column 、Fiex 内,并且从Expanded 到封装它的Row 、Column 、Flex 的路径必须只包括StatelessWidgets 或者StatefulWidgets (不能是其他类型的组件,像RenderObjectWidget ,它是渲染对象,不再改变尺寸,因此Expanded 不能放进RenderObjectWidget ),示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dR6OGRB-1630141808024)(https://user-gold-cdn.xitu.io/2019/2/10/168d6b3ab4c510a4?imageView2/0/w/1280/h/960/ignore-error/1)]
class MyHomePage extends StatelessWidget {
....
body:new RowWidget(),
...
}
class RowWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Row(
children: <Widget>[
new RaisedButton(
onPressed: (){
},
color:Colors.green,
child:new Text('绿色按钮1')
),
new Expanded(
child:new RaisedButton(
onPressed: (){
},
color:Colors.yellow,
child:new Text('黄色按钮2')
),
),
new RaisedButton(
onPressed:(){
},
color:Colors.red,
child:new Text('黑色按钮3')),
],
);
}
}
运行效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WiItDvuE-1630141808025)(https://user-gold-cdn.xitu.io/2019/2/10/168d78690cdb210a?imageView2/0/w/1280/h/960/ignore-error/1)]
class MyHomePage extends StatelessWidget {
....
body:new RowWidget(),
...
}
class RowWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return Row(
children: <Widget>[
Expanded(
child:Container(
color:Colors.green,
padding:EdgeInsets.all(8),
height: 40.0,
),
flex:1,
),
Expanded(
child:Container(
color:Colors.yellow,
padding:EdgeInsets.all(8),
height: 40.0,
),
flex:2,
),
Expanded(
child:Container(
color:Colors.red,
padding:EdgeInsets.all(8),
height: 40.0,
),
),
],
);
}
}
上面代码设置了flex ,将一行的宽度分成四等分,第一、三child 占1/4的区域,第二个child 占1/2区域。 效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J1BmtwTs-1630141808025)(https://user-gold-cdn.xitu.io/2019/2/10/168d797afd2c9fc4?imageView2/0/w/1280/h/960/ignore-error/1)]
8.ListView
我相信这个布局在平时开发会经常用到,这是可滚动的列表控件,ListView 是最常用的滚动widget ,它在滚动方向上一个接一个地显示它的孩子。在纵轴上,孩子没被要求填充ListView ,并且内置ListTitle ,示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDhCrS19-1630141808025)(https://user-gold-cdn.xitu.io/2019/2/10/168d7a145363861c?imageView2/0/w/1280/h/960/ignore-error/1)]
class MyHomePage extends StatelessWidget {
....
body: new ListViewWidget(
new List<String>.generate(1000,(i){
return 'Item &i';
}),
),
...
}
//ListView
class ListViewWidget extends StatelessWidget {
final List<String> items;
ListViewWidget(this.items);
@override
Widget build(BuildContext context) {
return new ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return new ListTile(
title: new Text('This is $index'),
);
},
);
}
}
效果图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fdnORLAI-1630141808026)(https://user-gold-cdn.xitu.io/2019/2/10/168d7bb93cdc7fb3?imageView2/0/w/1280/h/960/ignore-error/1)]
下面设置水平的ListView :
class MyHomePage extends StatelessWidget {
....
body: new ListViewWidget(
new List<String>.generate(1000, (i) {
return 'Item &i';
}),
),
...
}
Widget build(BuildContext context) {
return new ListView.builder(
itemCount: items.length,
//设置水平方向
scrollDirection:Axis.horizontal,
//竖直时:确定每一个item的高度
//水平时:确定每一个item的宽度 得要设置 不然不显示
itemExtent: 110.0,
itemBuilder: (context, index) {
return new ListTile(
title: new Text('This is $index'),
);
},
);
效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZJHrS7N-1630141808026)(https://user-gold-cdn.xitu.io/2019/2/10/168d7c93a67be1c2?imageslim)]
9.GridView
GridView 是一个网格布局的列组件。GridView 继承至CustomScrollView ,示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gwzlrw0Q-1630141808027)(https://user-gold-cdn.xitu.io/2019/2/10/168d7e30f14ad8c1?imageView2/0/w/1280/h/960/ignore-error/1)]
直接竖直上例子:
//GridView
class GridViewWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return new GridView.count(
crossAxisCount: 3, //3列
children: List.generate(40,
(i){
return Card(
child: Center(
child:Text('This is $i'),
),
);
})
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AcGe5AZn-1630141808027)(https://user-gold-cdn.xitu.io/2019/2/10/168d7ec415ccdeeb?imageView2/0/w/1280/h/960/ignore-error/1)]
下面上水平例子:
return new GridView.count(
//3行
crossAxisCount: 3,
//设置水平
scrollDirection: Axis.horizontal,
children: List.generate(40, (i) {
return Card(
child: Center(
child: Text('This is $i'),
),
);
}),
);
效果图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5aJQu9oo-1630141808027)(https://user-gold-cdn.xitu.io/2019/2/10/168d805ce19a63a6?imageView2/0/w/1280/h/960/ignore-error/1)]
10.TabBar
移动开发中tab 切换是一个很常用的功能,那么Flutter 有没有提供这个Widget 呢?答案是有的,Flutter 通过Material 库提供了很方便的API来使用tab 切换。
10.1.创建TabController
TabBarView 和TabBar 都有一个TabController 的参数,TabbarView 和TabBar 就是由TabController 来控制同步,点击某个Tab 后,要同步显示对应的TabBarView ,创建TabController 有两种方式:
- 使用系统自带的
DefaultTabController ,在Scaffold 套一层DefaultTabController ,这种方式TabBarView 会自动查找这个tabController 。 - 自己定义一个
TabController ,实现SingleTickerProviderStateMixin
下面就列一下第一种方式:
@override
Widget build(BuildContext context) {
return new DefaultTabController();
}
10.2.构建Tab数据
final List<Tab> myTabs = <Tab>[
new Tab(text: 'Android'),
new Tab(text: 'IOS'),
new Tab(text: 'Flutter'),
new Tab(text: 'RN'),
new Tab(text: 'Java'),
new Tab(text: 'C'),
new Tab(text: 'C++'),
new Tab(text: 'Go'),
];
10.3.创建TabBar
TabBar 在哪里都可以创建,在AppBar 里有一个bottom 参数可以接受TabBar ,就放在AppBar 下:
//设置appbar
appBar: new AppBar(
//底部
bottom: new TabBar(
indicatorColor: Colors.red, //指示器颜色 如果和标题栏颜色一样会白色
tabs: myTabs,//绑定数据
isScrollable: true, //是否可以滑动
),
),
10.4.绑定TabBar和TabBarView
class MyHomePage extends StatelessWidget {
final List<Tab> myTabs = <Tab>[
new Tab(text: 'Android'),
new Tab(text: 'IOS'),
new Tab(text: 'Flutter'),
new Tab(text: 'RN'),
new Tab(text: 'Java'),
new Tab(text: 'C'),
new Tab(text: 'C++'),
new Tab(text: 'Go'),
];
@override
Widget build(BuildContext context) {
return new DefaultTabController(
length: myTabs.length, //Tab长度
child: new Scaffold(
//设置appbar
appBar: new AppBar(
//底部
bottom: new TabBar(
indicatorColor: Colors.red, //指示器颜色 如果和标题栏颜色一样会白色
tabs: myTabs,//绑定数据
isScrollable: true, //是否可以滑动
),
....
),
body: new TabBarView(
//选中哪个Tabs,body就会显示
children: myTabs.map((Tab tab) {
return new Center(child: new Text(tab.text));
}).toList(),
),
....
);
}
}
效果如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qPkJY162-1630141808028)(https://user-gold-cdn.xitu.io/2019/2/11/168da97e44473a32?imageslim)]
11.BottomNavigationBar
BottomNavigationBar 即是底部导航栏控件,显示在页面底部的设计控件,用于在试图切换,底部导航栏包含多个标签、图标或者两者搭配的形式,简而言之提供了顶级视图之间的快速导航。
11.1.构建底部标签
//底部数据
final Map bottomMap ={
"首页":Icon(Icons.home),
"朋友圈":Icon(Icons.camera),
"信息":Icon(Icons.message),
"其他":Icon(Icons.devices_other),
};
11.2.创建导航栏
因为点击导航栏需要对应的字体显示,所以MyHomePage 需要继承StatefulWidget ,增加State ,
//用无状态控件显示
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//主题色
theme: ThemeData(
//设置为红色
primarySwatch: Colors.red),
//这是一个Widget对象,用来定义当前应用打开的时候,所显示的界面
home: MyHomePageWidget(),
);
}
}
class MyHomePageWidget extends StatefulWidget{
@override
State<StatefulWidget> createState(){
return new MyHomePage();
}
}
class MyHomePage extends State<MyHomePageWidget> {
//底部数据
final Map bottomMap ={
"首页":Icon(Icons.home),
"朋友圈":Icon(Icons.camera),
"信息":Icon(Icons.message),
"其他":Icon(Icons.devices_other),
};
int _index = 0;
bottomNavigationBar: BottomNavigationBar(
items: (){
var items = <BottomNavigationBarItem>[];
bottomMap.forEach((k,v){
items.add(BottomNavigationBarItem(
title:Text(k),//取map的值
icon : v,//取map的图标
backgroundColor:Colors.red,//背景红色
));
});
return items;
}(),
currentIndex: _index,//选中第几个
onTap:(position){
Fluttertoast.showToast(
msg: 'text inputted: $position',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
);
setState(() {
_index = position;//状态更新
});
}
),
}
最终效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yltjoNjK-1630141808028)(https://user-gold-cdn.xitu.io/2019/2/11/168dafdd5610d700?imageslim)]
五、实践
下面实践Flutter 中文网的例子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvFa0h65-1630141808029)(https://user-gold-cdn.xitu.io/2019/2/11/168db0f3a8ccc3b0?imageView2/0/w/1280/h/960/ignore-error/1)]
先上布局分析图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BBQlRHBd-1630141808029)(https://user-gold-cdn.xitu.io/2019/2/11/168db18086c6f6c9?imageView2/0/w/1280/h/960/ignore-error/1)]
1.实现图像
再说一下如何配置图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qguFGyoW-1630141808029)(https://user-gold-cdn.xitu.io/2019/2/11/168db1c21dda63da?imageView2/0/w/1280/h/960/ignore-error/1)]
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
home:new MyHomeWidget(),
);
}
}
class MyHomeWidget extends StatelessWidget{
@override
Widget build(BuildContext context){
return new Scaffold(
//设置标题栏
appBar: new AppBar(
title:new Text('Flutter Demo'),
),
//主体用ListView
body:new ListView(
children: <Widget>[
//图片
new Image.asset(
'images/lake.jpg',
width:600.0,
height:240.0,
//顺便设置图片属性
fit:BoxFit.cover,
)
],
),
);
}
}
2.实现标题栏
//实现标题栏
Widget titleWidget = new Container(
//内边距
padding:const EdgeInsets.all(30.0),
//整体是一个水平的布局
child:new Row(
//只有一个孩子
children: <Widget>[
//用Expanded 会占用icon之外剩余空间
new Expanded(
//垂直布局 放置两个文本
child: new Column(
//设置文本一起始端对齐
crossAxisAlignment: CrossAxisAlignment.start,
//有两个孩子
children: <Widget>[
new Container(
//底部内边距
padding:const EdgeInsets.only(bottom:10.0),
//孩子 设置字体样式
child:new Text(
'Oeschinen Lake Campground',
style: new TextStyle(fontWeight: FontWeight.bold),
),
),
new Text(
'Kandersteg, Switzerland',
style: new TextStyle(
color:Colors.grey[450],//设置颜色透明度
),
)
],
),
),
new Icon(
Icons.star,
color:Colors.red[400],
),
new Text('41'),
],
),
);
3.实现按钮行
因为三个按钮样式都是一样的,所以抽取公共部分:
/**
* 抽取button行的代码复用
*
*/
Column getText(IconData icon,String text){
return new Column(
//聚集widgets
mainAxisSize:MainAxisSize.min,
//child居中
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Icon(icon,color:Colors.blue[500]),
new Container(
//上部外边距
margin: const EdgeInsets.only(top:8.0),
//Text内容样式设定
child:new Text(
text,
style:new TextStyle(
color:Colors.blue[500],
),
),
)
],
);
}
/**
* 按钮实现
*/
Widget buttonWidget = new Container(
//三列
child:new Row(
//用MainAxisAlignment.spaceEvenly平均分配子空间
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
//孩子们
children: <Widget>[
getText(Icons.call, "CALL"),
getText(Icons.near_me, "ROUTE"),
getText(Icons.share, "SHARE"),
],
),
);
4.实现文本
/**
* 文本实现
*/
Widget textWidget = new Container(
alignment: Alignment.center,
//设置内边距
padding:const EdgeInsets.all(10.0),
child:new Text(
'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese Alps. Situated 1,578 meters above sea level, '
'it is one of the larger Alpine Lakes. A gondola ride from Kandersteg, '
'followed by a half-hour walk through pastures and pine forest, '
'leads you to the lake, which warms to 20 degrees Celsius in the summer. '
'Activities enjoyed here include rowing, and riding the summer toboggan run.',
// softWrap: true,//属性表示文本是否应在软换行符(例如句点或逗号)之间断开。
// textAlign: TextAlign.center,
),
);
5.整合
return new Scaffold(
//设置标题栏
appBar: new AppBar(
title:new Text('Flutter Demo'),
),
//主体用ListView
body:new ListView(
children: <Widget>[
//图片
new Image.asset(
'images/lake.jpg',
width:600.0,
height:240.0,
//顺便设置图片属性
fit:BoxFit.cover,
),
//标题栏
titleWidget,
//按钮栏
ke Oeschinen lies at the foot of the Blüemlisalp in the Bernese Alps. Situated 1,578 meters above sea level, '
'it is one of the larger Alpine Lakes. A gondola ride from Kandersteg, '
'followed by a half-hour walk through pastures and pine forest, '
'leads you to the lake, which warms to 20 degrees Celsius in the summer. '
'Activities enjoyed here include rowing, and riding the summer toboggan run.',
// softWrap: true,//属性表示文本是否应在软换行符(例如句点或逗号)之间断开。
// textAlign: TextAlign.center,
),
);
5.整合
return new Scaffold(
//设置标题栏
appBar: new AppBar(
title:new Text('Flutter Demo'),
),
//主体用ListView
body:new ListView(
children: <Widget>[
//图片
new Image.asset(
'images/lake.jpg',
width:600.0,
height:240.0,
//顺便设置图片属性
fit:BoxFit.cover,
),
//标题栏
titleWidget,
//按钮栏
|