Container 组件

相当于 HTML中的 div盒子!;

注意: 在 Container 中 使用 decoration 装饰 盒子 属性的时候,外面不能使用 color 属性来设置盒子背景了!

错误案例

1
2
3
4
5
6
7
8
9
10
Container(
child: Text('错误案例!'),

color: Colors.white,
// color 属性 和 decoration 属性 会出现冲突
decoration: BoxDecoration(
// 圆角
borderRadius: BorderRadius.circular(15)
)
)

正确案例

1
2
3
4
5
6
7
8
9
10
Container(
child: Text('错误案例!'),
// color: Colors.white,
// color 属性 和 decoration 属性 会出现冲突
decoration: BoxDecoration(
color: Colors.white,
// 圆角
borderRadius: BorderRadius.circular(15)
)
)

1
2
3
Container(
alignment: Alignment.center;
)
名称功能
topCenter顶部剧中对齐
topLeft顶部左对齐
topRight顶部右对齐
center水平垂直居中对齐
centerLeft垂直居中水平左对齐
centerRight垂直居中水平右对齐
bottomCenter底部居中对齐
bottomLeft底部左对齐
bottomRight底部右对齐
1
2
3
4
5
6
7
8
9
10
11
12
13
decoration: BoxDecoration(
color: Colors.blue, // 背景颜色
border: Border.all( color:Colors.red, width: 2.0 ), borderRadius:BorderRadius.circular(10),// 圆角,
boxShadow: [
BoxShadow(
color: Colors.blue, // 阴影的颜色
offset: Offset(2.0, 2.0), // 阴影的偏移(水平 垂直)
blurRadius: 10.0, // 阴影的模糊度
)
],
//LinearGradient 背景线性渐变 RadialGradient径向渐变
gradient: LinearGradient( colors: [Colors.red, Colors.orange],
)

margin属性是表示Container与外部其他组件的距离。 Edgelnsets.all(20.0),

1
2
3
4
Container(
// margin: const EdgeInsets.all(10); // 上 下 左 右
margin: const EdgeInsets.fromLTRB(10,3,4,5); // 上 下 左 右
)

padding就是Container的内边距,指Container边缘与Child之间的距离padding:Edgelnsets.all(10.0)

1
2
3
4
Container(
// padding: const EdgeInsets.all(10); // 上 下 左 右
padding: const EdgeInsets.fromLTRB(10,3,4,5); // 上 下 左 右
)

让Container容易进行一些旋转或者位移之类的transform: Matrix4.rotationZ(0.2)

1
2
3
4
5
Container(
// transform: Matrix4.translationValues(x,y,z); // 位移
// transform: Matrix4.rotationZ(0.2) // 旋转
transform: Matrix4.skewY(0.2) // 倾斜
)

设置容器的高度

1
2
3
Container(
height: 100; // 设置 double.infinity 覆盖整个屏幕的高度
)

设置容器的宽度

1
2
3
Container(
width: 100; // 设置 double.infinity 覆盖整个屏幕的宽度
)

设置容器的子组件

1
2
3
Container(
child: Text('Widget 组件!'); // 上 下 左 右
)

main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'package:flutter/material.dart';

import './container.dart';

void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('container demo'),
),
body: const MyContainerApp()
),
);
}
}

container.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import 'package:flutter/material.dart';


// 构建一个组件
class MyContainerApp extends StatelessWidget {
const MyContainerApp({super.key});

@override
Widget build(BuildContext context) {
return Center(
child: Container(
alignment: Alignment.center,
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blueAccent,
border: Border.all(
color: Colors.black,
),
borderRadius: BorderRadius.circular(10),
boxShadow: const [
BoxShadow(
color: Colors.black,
offset: Offset(12, 23),
blurRadius: 12.2
)
],
gradient: const LinearGradient(colors: [Colors.blue, Colors.transparent])
),
child: const Text(
'Container box!',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold
),

),
),
);
}
}

Container 组件 只能有一个 子组件,想加入多个组件,可以配合 column 或者 row 组件 实现!

1
2
3
4
5
6
7
8
9
Container(
width: 150,
height: 100,
child: Column(
children: [
...
]
)
)

Text 组件

名称功能
textAlign文本对齐方式 (center居中,left左对齐,right右对齐,justfy两端对齐)
textDirection文本方向 (ltr从左至右,rtl从右至左)
overflow文字超出屏幕之后的处理方式 (clip裁剪,fade渐隐,ellipsis省略号)
textScaleFactor字体显示倍率
maxLines文字显示最大行数
style字体的样式设置

TextStyle 组件

名称功能
decoration文字装饰线 (none没有线,lineThrough删除线,overline上划线underline下划线)
decorationColor文字装饰线颜色
decorationStyle文字装饰线风格 ([dashed,dotted]虚线,double两根线,solid一根实线wavy波浪线)
wordSpacing单词间隙 (如果是负值,会让单词变得更紧凑
letterSpacing字母间隙 (如果是负值,会让字母变得更紧凑)
fontStyle文字样式 (italic斜体,normal正常体)
fontSize文字大小
color文字颜色
fontWeight字体粗细 (bold粗体,normal正常体)

Text Demo

main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import 'package:flutter/material.dart';

// import './container.dart';
import './textDemo.dart';

void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('text demo'),
),
body: const MyTextDemoApp()
),
);
}
}

testDemo.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import 'package:flutter/material.dart';


class MyTextDemoApp extends StatelessWidget {
const MyTextDemoApp({super.key});

@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
color: Colors.blue
),
child: const Text(
'Text Components Demo !',
textAlign: TextAlign.center, // 文字居中
overflow: TextOverflow.ellipsis, // 文字溢出
textScaleFactor: 1.6, // 显示倍率
style: TextStyle(
color: Colors.white, // 字体颜色
fontWeight: FontWeight.bold, // 字体加粗
decoration: TextDecoration.underline, // 字体下划线
decorationColor: Colors.amber, // 下划线颜色
decorationStyle: TextDecorationStyle.dashed, // 下划线 为虚线
wordSpacing: 5, // 单词间距
),
),
)
);
}
}

Image 组件

Image.network 远程图片

参数为 src

1
Image.network(src)

常用属性

名称类型说明
alignmentAlignment图片的对齐方式
color 和 colorBlendMode$nbsp;设置图片的背景颜色,通常和colorBlendMode配合一起使用,这样可以是图片颜色和背景色混合。上面的图片就是进行了颜色的混合,绿色背景和图片红色的混合
fitBoxFitfit属性用来控制图片的拉伸和挤压,这都是根据父容器来的。 BoxFit.fill:全图显示,图片会被拉伸,并充满父容器。BoxFit.contain:全图显示,显示原比例,可能会有空隙。BoxFit.cover: 显示可能拉伸,可能裁切,充满 (图片要充满整个容器,还不变形)。 BoxFit.fitWidth: 宽度充满 (横向充满),显示可能拉伸,可能裁切。 BoxFit.fitHeight:高度充满 (竖向充满),显示可能拉伸,可能裁切。BoxFit.scaleDown: 效果和contain差不多,但是此属性不允许显示超过源图片大小,可小不可大。
repeatlmageRepeat平铺: lmageRepeat.repeat: 横向和纵向都进行重复,直到铺满整个画布。ImageRepeat.repeatx: 横向重复,纵向不重复lmageRepeat.repeatY: 纵向重复,横向不重复。
width宽度宽度 一般结合ClipOval才能看到效果
height高度高度 一般结合ClipOval才能看到效果
Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import 'package:flutter/material.dart';

void main(List<String> args) {
return runApp(MyImageApp());
}

class MyImageApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Image demo",
home: Scaffold(
appBar: AppBar(
title: Text("Image demo"),
),
body: Center(
child: Container(
child: Image.network(
"https://img3.sycdn.imooc.com/szimg/6200d54009de302412000676-360-202.png",
// 图片缩放
scale: 1.5,
fit: BoxFit.cover,
// 图片填充方式 cover contain fill fitWidth
// fitWidth repeat 失效
// fit: BoxFit.contain,
// 颜色混合配合使用
// color: Colors.greenAccent,
// 颜色混合模式
// colorBlendMode: BlendMode.colorDodge,
// 图像平铺 repeat repeatY repeatX
repeat: ImageRepeat.repeatY,
),
width: 400.0,
height: 300.0,
// color: Colors.amberAccent,
padding: EdgeInsets.all(15.0),
// 盒子装饰
decoration: BoxDecoration(
color: Colors.amberAccent,
),
)
)
)
);
}
}

main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'package:flutter/material.dart';
// import './container.dart';
// import './textDemo.dart';
import './image.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('image network demo'),
),
body: const MyClipOvalImage()
),
);
}
}

image.dart
第一种使用 Container 中的 decoration 装饰,给盒子加上圆角,并添加背景图(image: DecorationImage)!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 圆形图片
class MyCircularImage extends StatelessWidget {
const MyCircularImage({super.key});

@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(100),
image: const DecorationImage(
image: NetworkImage("https://img3.sycdn.imooc.com/szimg/6200d54009de302412000676-360-202.png"),
fit: BoxFit.cover
)
),
),
);
}
}

第二种使用 ClipOval 类来实现圆形图!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用 ClipOval
class MyClipOvalImage extends StatelessWidget {
const MyClipOvalImage({super.key});

@override
Widget build(BuildContext context) {
return Center(
child: ClipOval(
child: Image.network(
"https://img3.sycdn.imooc.com/szimg/6200d54009de302412000676-360-202.png",
width: 150,
height: 150,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
),
),
);
}
}

第三种使用 CircleAvatar 类来实现圆形图!

1
2
3
CircleAvatar(
backgroundImage: NetworkImage("imageUrl"),
),

加载本地图片

1
Image.asset('images/a.jpeg', fit: BoxFit.cover);

1、在根目录下新建images文件夹

注意: images 文件夹下有 > 2.0x3.0x 文件夹! 每一张图片都会在 2.0x3.0x文件夹内!

2、然后在这个文件里pubspec.yaml配置下assets路径

图标组件

Flutter官方Icon网址无法正常打开,在这边网上提供了Icon的官方截图可以参照下! Icon 参考截图

内置图标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'package:flutter/material.dart';

class MyIconApp extends StatelessWidget {
const MyIconApp({super.key});

@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: const [
Text('内置组件', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.black, fontSize: 19)),
Icon(Icons.add_comment),
Icon(Icons.home),
Icon(Icons.airplanemode_off, size: 46, color: Colors.blue)
],
),
);
}
}

自定义图标

我们可以在这里下载我们想要的 Icon 阿里图标库

1、在根目录创建fonts文件夹
2、下载的图标文件jsontff文件结尾的放入fonts文件夹内
3、在pubspec.yaml这个文件内部加入fonts配置

4、创建一个自定义类ityingFont

通过 IconData 配置自定义Icon

5、使用ityingFont

1
2
3
import './ityingFont.dart'

Icon(ItyingFont.book, size: 40, color: Colors.red)

注意:

.json 文件内有需要 unicode对应的编码!unicode编码在IconData中需要[0x3347]! 编码格式0x[unicode]

ListView 组件

列表布局是我们项目开发中最常用的一种布局方式。Flutter中我们可以通过ListView来定义列表项,支持垂直和水平方向展示。通过一个属性就可以控制列表的显示方向。列表有以下分类:

  1. 垂直列表
  2. 垂直图文列表
  3. 水平列表
  4. 动态列表

在 [listView] 中, 当列表为 水平 方向滚动时,[Container] 下的 [height] 是自适应的,设置无效!

在 [listView] 中, 当列表为 垂直 方向滚动时,[Container] 下的 [width] 是自适应的,设置无效!

若想控制 ListView 的宽高,需要借助其他组件来限制,SizeBox() or Container()

1
2
3
4
5
6
7
8
9
SizeBox(
width: 120,
height: 120,
child: ListView(
children: [
...
]
)
)

列表中常用的参数

名称类型说明
scrollDirectionAxisAxis.horizontal 水平列表 Axis.vertical 垂直列表
paddingEdgelnsetsGeometry内边距
resolvebool组件反向排序
childrenList列表元素

普通列表

使用 ListViewListTile 实现一个普通列表

main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import 'package:flutter/material.dart';

// import './container.dart';
// import './textDemo.dart';
// import './image.dart';
// import './icon.dart';
import './listView.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ListView demo'),
),
body: const MyListViewDemo()
),
);
}
}

listView.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyListViewDemo extends StatelessWidget {
const MyListViewDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView(
children: const [
ListTile(title: Text('hello listview~')),
Divider(),
ListTile(title: Text('hello listview~')),
Divider(),
ListTile(title: Text('hello listview~')),
Divider(),
ListTile(title: Text('hello listview~')),
Divider(),
ListTile(title: Text('hello listview~')),
Divider(),
ListTile(title: Text('hello listview~')),
Divider(),
],
);
}
}

Icon列表

前置图标leading参数
后置图标trailing参数

leading 参数可以实现 在列表左侧安放任意组件[image 、icon...]
trailing 参数可以实现 在列表右侧安放任意组件[image 、icon...]

main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import 'package:flutter/material.dart';

// import './container.dart';
// import './textDemo.dart';
// import './image.dart';
// import './icon.dart';
import './listView.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ListView demo'),
),
body: const MyListViewDemo()
),
);
}
}

listView.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class MyListViewDemo extends StatelessWidget {
const MyListViewDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView(
children: const [
ListTile(
leading: Icon(Icons.home, color: Colors.lightBlue,),
title: Text('首页'),
trailing: Icon(Icons.chevron_right_sharp),
),
Divider(),
ListTile(
leading: Icon(Icons.local_car_wash, color: Colors.orange,),
title: Text('待收货'),
trailing: Icon(Icons.chevron_right_sharp),
),
Divider(),
ListTile(
leading: Icon(Icons.payment, color: Colors.blue,),
title: Text('待付款'),
trailing: Icon(Icons.chevron_right_sharp),
),
Divider(),
ListTile(
leading: Icon(Icons.favorite,color: Colors.green),
title: Text('我的收藏'),
trailing: Icon(Icons.chevron_right_sharp),
),
Divider(),
ListTile(
leading: Icon(Icons.people, color: Colors.black45,),
title: Text('在线客服'),
trailing: Icon(Icons.chevron_right_sharp),
),
Divider(),
],
);
}
}

图文列表

subtitle参数可以实现子标题

main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import 'package:flutter/material.dart';

// import './container.dart';
// import './textDemo.dart';
// import './image.dart';
// import './icon.dart';
import './listView.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ListView demo'),
),
body: const MyListViewDemo()
),
);
}
}

listView.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class MyListViewDemo extends StatelessWidget {
const MyListViewDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.fromLTRB(0,10,0,0),
children: [
ListTile(
leading: Image.network("https://img3.sycdn.imooc.com/szimg/6200d54009de302412000676-360-202.png"),
title: const Text(
'图文列表图文列表图文列表图文列表',
overflow: TextOverflow.ellipsis,
),
subtitle: const Text(
'子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: const Icon(Icons.chevron_right_sharp),
),
const Divider(),

ListTile(
leading: Image.network("https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"),
title: const Text(
'图文列表图文列表图文列表图文列表',
overflow: TextOverflow.ellipsis,
),
subtitle: const Text(
'子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: const Icon(Icons.chevron_right_sharp),
),
const Divider(),

ListTile(
leading: Image.network("https://img4.mukewang.com/szimg/63773e56090d2b2205400304.png"),
title: const Text(
'图文列表图文列表图文列表图文列表',
overflow: TextOverflow.ellipsis,
),
subtitle: const Text(
'子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容子标题内容',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: const Icon(Icons.chevron_right_sharp),
),
const Divider(),
],
);
}
}

listView.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class MyListViewDemo extends StatelessWidget {
const MyListViewDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.fromLTRB(5,10,0,5),
children: [
Image.network("https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"),
Container(
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
height: 44,
child: const Text('I am title~', textAlign: TextAlign.center, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),
),
Image.network("https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"),
Container(
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
height: 44,
child: const Text('I am title~', textAlign: TextAlign.center, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),
),
Image.network("https://img4.mukewang.com/szimg/63773e56090d2b2205400304.png"),
Container(
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
height: 44,
child: const Text('I am title~', textAlign: TextAlign.center, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),
),
],
);
}
}

水平滑动列表

通过scrollDirection: Axis.horizontal此参数来控制 水平垂直方向!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyListViewDemo extends StatelessWidget {
const MyListViewDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView(
// padding: EdgeInsets.fromLTRB(5,10,5,0),
`scrollDirection`: Axis.horizontal,
children: [
Image.network("https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"),
Image.network("https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"),
Image.network("https://img4.mukewang.com/szimg/63773e56090d2b2205400304.png"),
],
);
}
}

动态列表

通过 List 方式添加ListTile
./data/listData.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List resData = [
{
"title": "title_1",
"author": "author_1" ,
"imageUrl": "https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"
},
{
"title": "title_2",
"author": "author_2" ,
"imageUrl": "https://img3.mukewang.com/szimg/60dea91109143a3005400304.png"
},
{
"title": "title_3",
"author": "author_3" ,
"imageUrl": "https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"
}
];
dynamicListView.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
List<Widget> initListData(){
List<Widget> result = [];
for (var item in resData) {
result.add(
ListTile(
title: Text(item['title']),
subtitle: Text(item['author']),
leading: Image.network(item['imageUrl']),
)
);
}
return result;
}

class MyDynamicListViewDemo extends StatelessWidget {
const MyDynamicListViewDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView(
children: initListData(),
);
}
}
main.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import 'package:flutter/material.dart';

// import './container.dart';
// import './textDemo.dart';
// import './image.dart';
// import './icon.dart';
// import './listView.dart';
import './dynamicListView.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ListView demo'),
),
body: const MyDynamicListViewDemo()
),
);
}
}

使用ListView.builder构建生成

ListView.builder 有两个参数是必须的:
itemCount: 数组的长度
itemBuilder: 迭代器函数, return 的是 widget 部件!

resData 这里的数据 同 ./data/listData.dart

使用 同 ./main.dart!

结果 最终都是一样的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyDynamicListViewDemo extends StatelessWidget {
const MyDynamicListViewDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: resData.length,
itemBuilder: ((context, index) {
return ListTile(
title: Text(resData[index]['title']),
subtitle: Text(resData[index]['author']),
leading: Image.network(resData[index]['imageUrl']),
);
})
);
}
}

GridView 组件

GridView 是网格布局组件! 有三种方式可以实现!
GridView.count: crossAxisCount 参数用来控制 一行显示几个子组件!
GridView.extent: maxCrossAxisExtent 参数用来控制,每个子组件的(长度)宽度,根据宽度来分配一行可以占几个子组件!
GridView.builder

通过GridView.count实现网格布局

main.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import 'package:flutter/material.dart';

// import './container.dart';
// import './textDemo.dart';
// import './image.dart';
// import './icon.dart';
// import './listView.dart';
// import './dynamicListView.dart';
import './gridView.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ListView demo'),
),
body: const MyGridViewDemo()
),
);
}
}
gridView.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 实现动态创建 Container 组件
List<Widget> initContainerCompList(){
List<Widget> result = [];
for (var i = 0; i < 20; i++) {
result.add(
Container(
alignment: Alignment.center,
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(15),
boxShadow:const [
BoxShadow(
color: Colors.black,
offset: Offset(0, 0),
blurRadius: 10,
)
]
),
child: Text(
'我是第${i+1}元素',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold
),
),
)
);
}
return result;
}
class MyGridViewDemo extends StatelessWidget {
const MyGridViewDemo({super.key});

@override
Widget build(BuildContext context) {
return GridView.count(
// 设置内边距
padding: EdgeInsets.all(10),
// 水平之间间距
crossAxisSpacing: 10,
// 垂直之间间距
mainAxisSpacing: 10,
// 一行显示 2 个 元素
crossAxisCount: 2,
// 设置子元素宽高比
childAspectRatio: 1.2
children: initContainerCompList(),
);
}
}

通过GridView.extent实现网格布局

GridView.extent 构造函数内部使用了 sliverGridDelegateWithMaxCrossAxisExtent, 我们通过它可以快速的创建横轴子元素为固定最大长度GridView
maxCrossAxisExtent 参数 每个元素固定最大长度 为 120!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyGridViewDemo extends StatelessWidget {
const MyGridViewDemo({super.key});

@override
Widget build(BuildContext context) {
return GridView.extent(
// 设置内边距
padding: EdgeInsets.all(10),
// 水平之间间距
crossAxisSpacing: 10,
// 垂直之间间距
mainAxisSpacing: 10,
// 每个元素固定最大长度 为 120
maxCrossAxisExtent: 120
// 设置子元素宽高比
childAspectRatio: 1.2
children: initContainerCompList(),
);
}
}

通过GridView.builder实现动态网格布局

类似于 ListView.builder
gridDelegate: 其中这个参数,需要 SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent 这两个类来修饰!
通过这两个类,可以分别实现GridView.countGridView.extent 这两个功能!

准备数据./data/listData.dart

listData.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
List resData = [
{
"title": "title_1",
"author": "author_1" ,
"imageUrl": "https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"
},
{
"title": "title_2",
"author": "author_2" ,
"imageUrl": "https://img3.mukewang.com/szimg/60dea91109143a3005400304.png"
},
{
"title": "title_3",
"author": "author_3" ,
"imageUrl": "https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"
},
{
"title": "title_4",
"author": "author_4" ,
"imageUrl": "https://img2.mukewang.com/szimg/63a26398099ff25405400304.png"
},
{
"title": "title_5",
"author": "author_5" ,
"imageUrl": "https://img1.mukewang.com/szimg/639989a5081b677f05400304.jpg"
},
{
"title": "title_6",
"author": "author_6" ,
"imageUrl": "https://img4.mukewang.com/szimg/632d59840927c04b05400304.png"
}
];
main.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import 'package:flutter/material.dart';

// import './container.dart';
// import './textDemo.dart';
// import './image.dart';
// import './icon.dart';
// import './listView.dart';
// import './dynamicListView.dart';
import './gridView.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('GridViewBuilder demo'),
),
body: const MyGridViewBuilderDemo()
),
);
}
}
gridView.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import './data/listData.dart';

Widget initGridViewData(context, index){
return Container(
padding: const EdgeInsets.all(10),
// margin: const EdgeInsets.all(5),
width: 120,
// height: 120,
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
width: 1,
),
borderRadius: BorderRadius.circular(15),
),
child: Column(
children: [
Image.network(resData[index]['imageUrl']),
const SizedBox(height: 10),
Text(
resData[index]['title'],
style: const TextStyle(
fontSize:14,
fontWeight: FontWeight.bold
)
)
],
),
);
}

class MyGridViewBuilderDemo extends StatelessWidget {
const MyGridViewBuilderDemo({super.key});

@override
Widget build(BuildContext context) {
return GridView.builder(
// 设置内边距
padding: const EdgeInsets.all(10),
// 水平之间间距
// crossAxisSpacing: 10,
// 垂直之间间距
// mainAxisSpacing: 10,
// 一行显示 2 个 元素
// crossAxisCount: 2,
// 设置子元素宽高比
// childAspectRatio: 1.2,
// SliverGridDelegateWithFixedCrossAxisCount or SliverGridDelegateWithFixedCrossAxisExtent
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.3,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
// 需要一个 Widget Function
// itemBuilder: (context, index){
// return Container();
// },
itemCount: resData.length,
itemBuilder: initGridViewData,
);
}
}

常用的属性

名称类型说明
scrollDirectionAxis滚动方法
paddingEdgelnsetsGeometry内边距
resolvebool组件反向排序
crossAxisSpacingdouble水平子Widget之间间距
mainAxisSpacingdouble垂直子Widget之间间距
crossAxisCountint 用在 GridView.count一行的Widget数量
maxCrossAxisExtentdouble 用在 GridView.extent横轴子元素的最大长度(设置每个子元素的宽度!)
childAspectRatiodouble子Widget宽高比例 默认为 [1 正方形] [> 1 高矮 宽长] [< 1 高长 宽窄 ]
childrenWidget List[]
gridDelegateSliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent控制布局主要用在GridView.builder里面

页面布局组件

padding 组件

给内容四周加上边距!

Container 组件

1
2
3
4
Container(
padding: EdgeInsets.all(10);
child: Text('padding test!');
)

Padding 组件
1
2
3
4
Padding(
padding: EdgeInsets.all(10);
child: Text('padding test!');
)

注意: 如果只是单方面给文本加 边距 的话 推荐使用 Padding 组件,相对于 Container组件 来说,Padding 组件内存占用少!

Row 组件

水平布局 组件 width 宽度自适应

Row 组件 如果没有 Container 组件包裹,就是自适应的,如果被 Container 包裹,那自适应就相对于 Container组件!
属性说明
mainAxisAlignment横(水平)轴的排序方式
crossAxisAlignment纵(垂直)轴的排序方式
children组件元素 []
main.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import 'package:flutter/material.dart';

// import './container.dart';
// import './textDemo.dart';
// import './image.dart';
// import './icon.dart';
// import './listView.dart';
// import './dynamicListView.dart';
// import './gridView.dart';
import './row.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Row demo'),
),
body: const MyRowAppDemo()
),
);
}
}
row.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import 'package:flutter/material.dart';

class MyRowAppDemo extends StatelessWidget {
const MyRowAppDemo({super.key});

@override
Widget build(BuildContext context) {
return Container(
child: Row(
// 主轴排列
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// 次轴排列
crossAxisAlignment: CrossAxisAlignment.end,
children: [
IconContainer(Icons.home),
IconContainer(Icons.account_balance_sharp, color: Colors.deepPurple),
IconContainer(Icons.air, color: Colors.lightGreenAccent),
],
),
);
}
}

// 封装一个Icon组件
class IconContainer extends StatelessWidget {
IconData icon;
Color color;

IconContainer(this.icon, {super.key, this.color=Colors.blueGrey});

@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
width: 100,
height: 100,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(15)
),
child: Icon(icon),
);
}
}

Column 组件

垂直布局 组件 height 高度自适应, 参数属性 基于 Row 相似!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import 'package:flutter/material.dart';

class MyColumnAppDemo extends StatelessWidget {
const MyColumnAppDemo({super.key});

@override
Widget build(BuildContext context) {
return Container(
child: Column(
// 主轴排列
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// 次轴排列
crossAxisAlignment: CrossAxisAlignment.end,
children: [
IconContainer(Icons.home),
IconContainer(Icons.account_balance_sharp, color: Colors.deepPurple),
IconContainer(Icons.air, color: Colors.lightGreenAccent),
],
),
);
}
}

Flex 组件

Flex 组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用 Rowcolumn 会方便些,因为Rowcolumn 都继承自Flex,参数基本相同,所以能使用Flex的地方基本上都可以使用RowcolumnFlex 本身功能是很强大的,它也可以和 Expanded 组件配合实现弹性布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
`flex.dart` 在 `main.dart` 引入即可!
import 'package:flutter/material.dart';

class MyFlexDemo extends StatelessWidget {
const MyFlexDemo({super.key});

@override
Widget build(BuildContext context) {
return Flex(
// 水平 或者 垂直 方向排列
direction: Axis.horizontal,
children: [
Expanded(child: IconContainer(Icons.home), flex: 1,),
Expanded(child: IconContainer(Icons.account_balance_sharp, color: Colors.deepPurple), flex: 2,),
],
);
}
}


// 封装一个Icon组件
class IconContainer extends StatelessWidget {
IconData icon;
Color color;

IconContainer(this.icon, {super.key, this.color=Colors.blueGrey});

@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
width: 100,
height: 100,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(15)
),
child: Icon(icon),
);
}
}

这里的效果 与 下边 Row 水平方向 Expanded 效果一至的!

Expanded 组件

Expanded 组件内 的 child 组件 无论设置宽高都是无效的,最终以 flex为优先最高!

main.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import 'package:flutter/material.dart';

import './expanded.dart';
void main(List<String> args) {
return runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('expanded demo'),
),
body: const MyExpandedDemo()
),
);
}
}
expanded.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import 'package:flutter/material.dart';

class MyExpandedDemo extends StatelessWidget {
const MyExpandedDemo({super.key});

@override
Widget build(BuildContext context) {
// or column
return Row(
children: [
Expanded(child: IconContainer(Icons.home), flex: 1,),
Expanded(child: IconContainer(Icons.account_balance_sharp, color: Colors.deepPurple), flex: 2,),
],
);
}
}


// 封装一个Icon组件
class IconContainer extends StatelessWidget {
IconData icon;
Color color;

IconContainer(this.icon, {super.key, this.color=Colors.blueGrey});

@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
width: 100,
height: 100,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(15)
),
child: Icon(icon),
);
}
}

row:

这块实现的是 row 方向的排列布局!

column:

想要实现纵向排列,通过 `column` 替换掉 `row` 即可!

怎么实现一个右侧固定 左侧自适应的呢

固定那边的元素 可以不使用 Expanded 即可!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyExpandedDemo extends StatelessWidget {
const MyExpandedDemo({super.key});

@override
Widget build(BuildContext context) {
// or column
return Row(
children: [
Expanded(child: IconContainer(Icons.home), flex: 1,),
IconContainer(Icons.account_balance_sharp, color: Colors.deepPurple)
],
);
}
}

Flex Expanded 组件 Demo

flexExpandedDemo.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import 'package:flutter/material.dart';

class MyFlexExpandedDemo extends StatelessWidget {
const MyFlexExpandedDemo({super.key});

@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
alignment: Alignment.center,
margin: EdgeInsets.fromLTRB(0, 0, 0, 5),
width: double.infinity,
height: 200,
decoration: const BoxDecoration(color: Colors.lightBlueAccent),
child: const Text('Flex Expanded Demo', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),),
),
SizedBox(
height: 155,
child: Row(
children: [
Expanded(
flex: 2,
child: SizedBox(
width: 20,
child: Image.network(
"https://img3.mukewang.com/szimg/6380338b09edb25005400304.png", fit: BoxFit.cover,)
)
),
const SizedBox(width: 5,),
Expanded(
flex: 1,
child: Column(
// 主轴排列
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Image.network(
"https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"),
Image.network(
"https://img3.mukewang.com/szimg/6380338b09edb25005400304.png"),
],
),
)
],
),
)
],
);
}
}

Stack 层叠组件

通过 Stack 组件 可以实现元素堆叠!,配合 Positioned 组件 可以实现 元素定位! Positioned 被定位的元素是相对于外部容器定位,如果没有外部容器就相对于整个屏幕定位的!

属性

属性说明
alignment配置所有子元素显示的位置
children子组件集合

stack.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 层叠组件 配合 position 和 align 实现 定位布局
import 'package:flutter/material.dart';


class MyStackAppDemo extends StatelessWidget {
const MyStackAppDemo({super.key});

@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Container(
width: 200,
height: 200,
decoration: const BoxDecoration(
color: Colors.blue
),
),
Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
color: Colors.blueGrey
),
),
const Text('Hello Stack!', style: TextStyle(color:Colors.white, fontSize: 18, fontWeight: FontWeight.bold),)
],
);
}
}

StackPositioned

Positioned 组件 可以设置 子组件 left top right bottom child width height!
Positioned 组件中内部子组件 如果是 Row 行组件的话 需要设置 宽度高度!
注意: Positioned 中的 width 参数 无法设置 double.infinity,若想设置屏幕的宽度和高度的话需要借助MediaQuery了!
注意: 宽度高度必须是固定值 没法使用 double.infinity!

stack.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 层叠组件 配合 position 和 align 实现 定位布局
import 'package:flutter/material.dart';

class MyStackAppDemo extends StatelessWidget {
const MyStackAppDemo({super.key});

@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Container(
width: 200,
height: 200,
decoration: const BoxDecoration(color: Colors.blue),
),
Positioned(
top: 15,
left: 20,
child: Container(
width: 100,
height: 100,
decoration: const BoxDecoration(color: Colors.blueGrey),
),
),
const Positioned(
bottom: 15,
right: 15,
child: Text(
'Hello Stack!',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold),
))
],
);
}
}

MediaQuery 获取设备的宽度高度
final size = MediaQuery.of(context).size

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class MyStackAppDemo extends StatelessWidget {
const MyStackAppDemo({super.key});
@override
Widget build(BuildContext context) {
// 获取 设备的宽高
final size = MediaQuery.of(context).size;
// size.width or size.height
return Stack(
alignment: Alignment.center,
children: [
Container(
width: 200,
height: 200,
decoration: const BoxDecoration(color: Colors.blue),
),
Positioned(
width: size.widht, // 设备的宽度
top: 15,
left: 20,
child: Container(
width: 100,
height: 100,
decoration: const BoxDecoration(color: Colors.blueGrey),
),
),
const Positioned(
bottom: 15,
right: 15,
child: Text(
'Hello Stack!',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold),
))
],
);
}
}

注意: stack 组件 是 没有确定性宽高的,所以需要使用确定宽高的容器[SizeBox] 和 [Container] 来包裹! 这样在做定位的时候,就有相对的容器可以定位!
stack 组件 是可以 直接 在跟节点使用的,这样他的相对定位的容器,是整个应用界面!

Align 组件

Align 组件可以调整子组件的位置,Stack组件中结合Align组件也可以控制每个子元素的显示位置
Align 参数:
alignment: 配置所有子元素的显示位置
child: 子组件

Contaienr 中使用 Align

之前文本居中 是这么操作的!

1
2
3
4
5
6
7
8
9
Container(
alignment:Alignment.center
width: 200,
height: 200,
decoration: Boxdecoration(
color: Colors.blue,
),
child: Text('Hello Align!')
)

使用 Align 组件

1
2
3
4
5
6
7
8
9
10
11
Container(
width: 200,
height: 200,
decoration: Boxdecoration(
color: Colors.blue,
)
child: Align(
alignment:Alignment.center
child: Text('Hello Align!')
)
)

Alignment 不仅可以使用静态属性 还可以通过参数来指定方位
alignment:Alignment(1,1) //右下 方位
alignment:Alignment(-1,1) //左下 方位
算法: Alignment.x*childwidth/2+childwidth/2,Alignment.y*childHeight/2+childHeight/2)

1
2
3
4
5
6
7
8
9
10
11
Container(
width: 200,
height: 200,
decoration: Boxdecoration(
color: Colors.blue,
)
child: Align(
alignment:Alignment(1,1) // 右下
child: Text('Hello Align!')
)
)

Stack and Align Demo

align.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import 'package:flutter/material.dart';

class MyAlignAppDemo extends StatelessWidget {
const MyAlignAppDemo({super.key});

@override
Widget build(BuildContext context) {
return Stack(
children: const [
Align(
alignment: Alignment.topLeft,
child: Text('left content!'),
),
Align(
alignment: Alignment.topRight,
child: Text('right content!'),
),
],
);
}
}

class MyAlignAppDemo2 extends StatelessWidget {
const MyAlignAppDemo2({super.key});

@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
width: double.infinity,
height: 40,
child: Stack(
children: const [
Align(
alignment: Alignment.topLeft,
child: Text('Hello Align!'),
),
Align(
alignment: Alignment.topRight,
child: Text('Hello Align!'),
)
],
),
)
],
);
}
}

AspectRatio

设置子元素的 宽高比
AspectRatio: 的作用是根据设置调整子元素child的宽高比。
AspectRatio: 首先会在布局限制条件允许的范围内尽可能的扩展,widget的高度是由宽度和比率决定的,类似于BoxFit中的contain,按照固定比率去尽量占满区域。
如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio最终将会去优先适应布局限制条件,而忽略所设置的比率。

属性说明
aspectRatio宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许按照这种比率进行布局,这只是一个参考值
child子组件

aspectRatio.dart

aspectRatio.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 控制子元素的宽高比

import 'package:flutter/material.dart';

class MyAspectRatioDemo extends StatelessWidget {
const MyAspectRatioDemo({super.key});

@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 2/1, // 宽 / 高
child: Container(
decoration: const BoxDecoration(
color: Colors.blue
),
),
);
}
}

Card 组件

Card是卡片组件块,内容可以由大多数类型的Widget构成,Card具有圆角和阴影,这让它看起来有立体感。

属性说明
margin外边距
child子组件
elevation阴影值的深度
color背景颜色
shadowColor阴影颜色
clipBehaviorclipBehavior 内容溢出的剪切方式 Clip.none不剪切 Clip.hardEdge裁剪但不应用抗据齿 Clip.antiAlias裁剪而且抗据齿 Clip.antiAliasWithSaveLayer带有抗锯齿的剪辑,并在剪辑之后立即保存saveLayer
ShapeCard的阴影效果,默认的阴影效果为圆角的长方形边shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10)))

aspectRatio.dart

cart.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 实现一个 卡片式布局
import 'package:flutter/material.dart';

class MyCardAppDemo extends StatelessWidget {
const MyCardAppDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(10),
children: [
MyCardCustomComponent(
imageUrl: "https://img1.mukewang.com/szimg/639989a5081b677f05400304.jpg",
title: "Card title 1",
subtitle: "this is subtitle 1~"
),
MyCardCustomComponent(
imageUrl: "https://img2.mukewang.com/szimg/63a26398099ff25405400304.png",
title: "Card title 2",
subtitle: "this is subtitle 2~"
),
MyCardCustomComponent(
imageUrl: "https://img4.mukewang.com/szimg/632d59840927c04b05400304.png",
title: "Card title 3",
subtitle: "this is subtitle 3~"
)
],
);
}
}

Widget MyCardCustomComponent(
{String imageUrl =
'https://img3.mukewang.com/szimg/6380338b09edb25005400304.png',
String title = 'Card title',
String subtitle = 'this is card components!'}) {

return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), // 设置圆角
margin: const EdgeInsets.only(bottom: 10), // 外边距
elevation: 20, // 设置阴影深度
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: AspectRatio(
aspectRatio: 5 / 3,
child: Image.network(imageUrl),
),
),
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(imageUrl),
),
title: Text(title,
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold)),
subtitle: Text(
subtitle,
style: const TextStyle(color: Colors.grey, fontSize: 12),
),
)
],
),
);
}

CircleAvatar 组件

CircleAvatar 可以实现一个圆形图片
有两个属性:
radius: 设置圆的半径
backgroundImage: 背景图片 backgroundImage: NetworkImage('src')
backgroundColor: 背景颜色

1
2
3
CircleAvatar(
backgroundImage: NetworkImage("imageUrl"),
),

Button 组件

普通按钮[ElevatedButton]

1
ElevatedButton(onPressed: (){}, child: Text('普通按钮')),

文本按钮[TextButton]

1
TextButton(onPressed: (){}, child: Text('文本按钮')),

边框按钮[OutlineButton]

1
OutlinedButton(onPressed: (){}, child: Text('边框按钮')),

图标按钮[IconButton]

1
IconButton(onPressed: (){}, icon: Icon(Icons.home))

带有图标的按钮

通过icon构造函数可以实现带有图标的按钮

1
2
3
ElevatedButton.icon(onPressed: (){}, label: Text('发送'), icon: const Icon(Icons.send)),
TextButton.icon(onPressed: (){}, label: Text('消息'), icon: const Icon(Icons.message),),
OutlinedButton.icon(onPressed: (){}, label: Text('添加'), icon: const Icon(Icons.add)),

自定义按钮样式

通过style: ButtonStyle()参数可以实现带有样式的按钮

可以修改按钮的 背景颜色文本颜色阴影内边距等样式…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ElevatedButton(
onPressed: () {},
child: const Text('带有样式按钮'),
style: ButtonStyle(
textStyle: // 文本样式
MaterialStateProperty.all(
const TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: // 背景颜色
MaterialStateProperty.all(Colors.deepOrange),
foregroundColor: // 文字和图标颜色
MaterialStateProperty.all(Colors.black)),
),
TextButton(
onPressed: () {},
child: Text('文本按钮'),
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black)),
),
OutlinedButton(
onPressed: (){},
child: Text('边框颜色'),
style: ButtonStyle(
side: MaterialStateProperty.all(
const BorderSide(color: Colors.black)
)
),
),
Container(
width: 95,
height: 50,
child: ElevatedButton(onPressed: () {}, child: Text('改变大小')),
),

设置按钮的宽高

此时需要使用Container or SizeBox控制按钮的大小!

1
2
3
4
5
6
7
8
9
10
11
12
Container(
width: 100,
height: 50,
child: ElevatedButton(onPressed: (){}, child: Text('大按钮')),
)
// or

SizeBox(
width: 100,
height: 50,
child: ElevatedButton(onPressed: (){}, child: Text('大按钮')),
)

占满全屏的按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
Row(
children: [
Expanded(
flex: 1,
child: SizedBox(
height: 40,
child: ElevatedButton(
onPressed: () {}, child: const Text('登录')
)
)
)
],
),

圆角按钮 和 圆形按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {},
child: Text('圆角按钮'),
style: ButtonStyle(shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)))),
),
SizedBox(
width: 60,
height: 60,
child: ElevatedButton(
onPressed: () {},
child: Text('圆形'),
style: ButtonStyle(shape: MaterialStateProperty.all(CircleBorder(side: BorderSide(color: Colors.blue)))),
),
),
],
)

buttons.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import 'package:flutter/material.dart';

class MyButtonsApp extends StatelessWidget {
const MyButtonsApp({super.key});

@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(10),
children: [
// -------------------------------------------------------------------------------------
const Text(
'普通按钮',
style: TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(onPressed: () {}, child: Text('普通按钮')),
OutlinedButton(onPressed: () {}, child: Text('边框按钮')),
TextButton(onPressed: () {}, child: Text('文本按钮')),
IconButton(onPressed: () {}, icon: Icon(Icons.home))
],
),
// -------------------------------------------------------------------------------------
const Text(
'带有图标按钮',
style: TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton.icon(
onPressed: () {},
label: Text('发送'),
icon: const Icon(Icons.send)),
OutlinedButton.icon(
onPressed: () {},
label: Text('添加'),
icon: const Icon(Icons.add)),
TextButton.icon(
onPressed: () {},
label: Text('消息'),
icon: const Icon(Icons.message),
),
],
),
// -------------------------------------------------------------------------------------
const Text(
'自定义样式按钮',
style: TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {},
child: const Text('带有样式按钮'),
style: ButtonStyle(
textStyle: // 文本样式
MaterialStateProperty.all(
const TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: // 背景颜色
MaterialStateProperty.all(Colors.deepOrange),
foregroundColor: // 文字和图标颜色
MaterialStateProperty.all(Colors.black)),
),
TextButton(
onPressed: () {},
child: Text('文本按钮'),
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black)),
),
OutlinedButton(
onPressed: (){},
child: Text('边框颜色'),
style: ButtonStyle(
side: MaterialStateProperty.all(
const BorderSide(color: Colors.black)
)
),
),
Container(
width: 95,
height: 50,
child: ElevatedButton(onPressed: () {}, child: Text('改变大小')),
),
],
),
// -------------------------------------------------------------------------------------
const Text(
'占满全屏的按钮',
style: TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
),

Divider(),
Row(
children: [
Expanded(
flex: 1,
child: SizedBox(
height: 40,
child: ElevatedButton(
onPressed: () {}, child: const Text('登录')
)
)
)
],
),
// -------------------------------------------------------------------------------------
const Text(
'圆角按钮 和 圆形按钮',
style: TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
),

Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {},
child: Text('圆角按钮'),
style: ButtonStyle(shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)))),
),
SizedBox(
width: 60,
height: 60,
child: ElevatedButton(
onPressed: () {},
child: Text('圆形'),
style: ButtonStyle(shape: MaterialStateProperty.all(CircleBorder(side: BorderSide(color: Colors.blue)))),
),
),
],
)
],
);
}
}

wrap 组件

wrap 组件 是ColumnRow 组合,有所不同的是,一行元素,当没有剩余空间时,可以自动折行!
Wrap可以实现流布局,单行的WrapRow表现几乎一致,单列的Wrap则跟Column表现几乎一致。但RowColumn都是单行单列的,Wrap则突破了这个限制,mainAxis上空间不足时,则向crossAxis上去扩展显示。

属性

属性说明
direction主轴的方向,默认水平
alignment主轴的对其方式
spacing主轴方向上的间距
textDirection文本方向
verticalDirection定义了children摆放顺序,默认是down,见Flex相关属性介绍.
runAlignmentrun的对齐方式。run可以理解为新的行或者列,如果是水平方向布局的话run可以理解为新的一行

Wrap Demo

wrapDemo.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import 'package:flutter/material.dart';

class MyWrapAppDemo extends StatelessWidget {
const MyWrapAppDemo({super.key});

@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(10),
children: [
Text('热搜', style: Theme.of(context).textTheme.headline5),
Divider(),
Wrap(
spacing: 15,
children: const [
MyButton(buttonText: '女装'),
MyButton(buttonText: '男装'),
MyButton(buttonText: '笔记本电脑'),
MyButton(buttonText: '手机'),
MyButton(buttonText: '手表'),
MyButton(buttonText: '香烟'),
MyButton(buttonText: '啤酒'),
],
),
Text('历史记录', style: Theme.of(context).textTheme.headline5),
const Divider(),
Padding(
padding: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment : CrossAxisAlignment.start,
children: [
// const Divider(color: Colors.black),

ListTile(title: Text('女装', style: Theme.of(context).textTheme.titleLarge),),

const Divider(),

ListTile(title: Text('笔记本', style: Theme.of(context).textTheme.titleLarge),),

const Divider(),
],
),
)
],
);
}
}


// 自定义 button 组件
class MyButton extends StatelessWidget {

final String buttonText;
final Color outlineColor;

const MyButton({super.key, required this.buttonText, this.outlineColor = Colors.lightBlue});

@override
Widget build(BuildContext context) {
return OutlinedButton(
onPressed: () {},
style: ButtonStyle(
side: MaterialStateProperty.all(BorderSide(
width: 2,
color: outlineColor
))
),
child: Text(buttonText),
);
}
}

StatefulWidget 有状态组件

通俗的讲: 如果我们想改变页面中的数据的话这个时候就需要用到StatefulWidget!
StatelessWidget 无法监听到数据的变化,并实时将数据更新到界面!
通过 Demo 实现一个 计数器
注意: setState(() {}); 这个方法每次触发后都会重新执行一遍Widget build!

main.dart

1
2
3
4
5
import './statefulWidget.dart';
void main(List<String> args) {
return runApp(const MyStatefulWidgetAppDemo());
}


statefulWidget.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import 'package:flutter/material.dart';


class MyStatefulWidgetAppDemo extends StatefulWidget {
const MyStatefulWidgetAppDemo({super.key});

@override
State<MyStatefulWidgetAppDemo> createState() => _MyStatefulWidgetAppDemoState();
}

class _MyStatefulWidgetAppDemoState extends State<MyStatefulWidgetAppDemo> {
int count = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('statefulWidget demo!'),),
body: Center(
child: Text('$count', style: Theme.of(context).textTheme.headline3),
),
// 右下角浮动按钮
floatingActionButton: FloatingActionButton(onPressed: () {
// 每次触发状态更新的时候,都会重新执行下 build 函数
setState(() {
count++;
});
},
child: const Icon(Icons.add),
),
),
);
}
}

Scaffold 组件

BottomNavigationBar 底部导航栏组件

属性
属性名描述
itemsList 底部导航条按钮集合
iconSizeicon 大小
currentIndex默认选中第几个
onTaptab 栏 点击事件
fixedColor选中的颜色
type如果 底部菜单 超过四个以上的话需要配置 type: BottomNavigationBarType.fixed or BottomNavigationBarType.shifting
Demo
  1. lib目录下新建文件夹pages/tabs
  2. 新建三个组件: home.dart setting.dart mine.dart
home.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Home page
import 'package:flutter/material.dart';


class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Home Page!', style: Theme.of(context).textTheme.headline5,),
);
}
}
setting.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// setting page
import 'package:flutter/material.dart';


class MySettingPage extends StatefulWidget {
const MySettingPage({super.key});

@override
State<MySettingPage> createState() => _MySettingPageState();
}

class _MySettingPageState extends State<MySettingPage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Setting Page!', style: Theme.of(context).textTheme.headline5,),
);
}
}
mine.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// mine page
import 'package:flutter/material.dart';


class MyMinePage extends StatefulWidget {
const MyMinePage({super.key});

@override
State<MyMinePage> createState() => _MyMinePageState();
}

class _MyMinePageState extends State<MyMinePage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Mine Page!', style: Theme.of(context).textTheme.headline5,),
);
}
}

bottomNavigationBar.dart中分别引入以上三个组件!

bottomNavigationBar.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// bottomNavigationBar 实现一个底部 Tab 栏
import 'package:flutter/material.dart';
import './pages/tabs/home.dart';
import './pages/tabs/setting.dart';
import './pages/tabs/mine.dart';
class MyBottomNavBarDemo extends StatefulWidget {
const MyBottomNavBarDemo({super.key});

@override
State<MyBottomNavBarDemo> createState() => _MyBottomNavBarDemoState();
}

class _MyBottomNavBarDemoState extends State<MyBottomNavBarDemo> {
int activeIndex = 0;
List<Widget> pages = [
const MyHomePage(),
const MySettingPage(),
const MyMinePage()
];
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
backgroundColor: Colors.black
),
home: Scaffold(
appBar: AppBar(title: const Text('BottomNavgiationBar Demo'),),
body: pages[activeIndex],
bottomNavigationBar: BottomNavigationBar(
// 当前索引值
currentIndex: activeIndex,
// 点击 tab 事件,value 为当前索引
onTap: (value) {
setState(() =>{
activeIndex = value
});
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
BottomNavigationBarItem(icon: Icon(Icons.people), label: '我的'),
]
),
),
);
}
}
main.dart
1
2
3
4
import './bottomNavigationBar.dart';
void main(List<String> args) {
return runApp(const MyBottomNavBarDemo());
}

FloatingActionButton 浮动按钮组件

FloatingActionButton 浮动按钮组件 在实现 计数器的时候用到过,这里配合 BottomNavigationBar 底部Tab组件实现 中间突起 按钮!

效果图

借助 Scaffold中的参数floatingActionButtonLocation来实现按钮位置更换!

main.dart

main.dart
1
2
3
4
import './floatingActionButton.dart';
void main(List<String> args) {
return runApp(const MyFloatButtonAppDemo());
}
`floatingActionButton.dart`
floatingActionButton.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import 'package:flutter/material.dart';

class MyFloatButtonAppDemo extends StatefulWidget {
const MyFloatButtonAppDemo({super.key});

@override
State<MyFloatButtonAppDemo> createState() => _MyFloatButtonAppDemoState();
}

class _MyFloatButtonAppDemoState extends State<MyFloatButtonAppDemo> {
int activeIndex = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
backgroundColor: Colors.black
),
home: Scaffold(
appBar: AppBar(title: const Text('FloatButtonAppDemo Demo'),),
body: Center(child: Text('FloatButtonAppDemo Demo', style: Theme.of(context).textTheme.headline5,),),
bottomNavigationBar: BottomNavigationBar(
// 当前选中的索引
currentIndex: activeIndex,
// 当 Tab 菜单栏超过 4个以上时需要设置类型
type: BottomNavigationBarType.fixed,
// 每个 tab 栏点击事件 value 为当前索引值
onTap: (value) {
setState(() {
activeIndex = value;
});
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
BottomNavigationBarItem(icon: Icon(Icons.people), label: '我的'),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: Container(
width: 70,
height: 70,
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(35)
),
child: FloatingActionButton(
// 如果 当前索引值 为 2 则为 激活 否则 失效激活
backgroundColor: activeIndex == 2 ? Colors.red : Colors.blue,
onPressed: () {
// 更改状态时 一定要加 setState
setState(() {
activeIndex = 2;
});
},
child: Icon(Icons.add),
),
)
),
);
}
}

注意:这里的案例 页面不会实现切换效果,若想实现,请看上面关于 [bottomNavigationBar]这个组件的有关案例!

Drawer 侧边栏组件

drawer: 可以配置 左侧 侧边栏!
endDrawer: 可以配置 右侧侧边栏!

DrawerHeader 组件

属性

属性描述
decoration设置顶部背景颜色
child配置子元素
padding内边距
margin外边距
UserAccountsDrawerHeader 组件
属性描述
decoration设置顶部背景颜色
accountName账户名称
accountEmail账户邮箱
currentAccountPicture用户头像
otherAccountsPictures用来设置当前账户其他账户头像
margin外边距

不同设备调试

flutter run 后根据提示,可在 控制台选择!

1
flutter run
1
flutter run -d chrome // 指定设备

vscode 右下角 会有个设备选择!

AppBar 组件

leading 可以配置 左侧 IconButton; actions 可以配置 右侧 Button!

属性

属性说明
leading在标题前面显示的一个控件,在首页通常显示应用的 logo; 在其他界面通常显示为返回按钮
title标题,通常显示为当前界面的标题文字,可以放组件
actions通常使用 IconButton 来表示,可以放按钮组
bottom通常放tabBar,标题下面显示一个 Tab 导航栏
backgroundColor导航背景颜色
iconTheme图标样式
centerTitle标题是否居中显示
elevation改变阴影的模糊深度!

控制 AppBar 的高度

PreferredSize 借助此类实现 控制 AppBar 高度!

1
2
3
4
5
6
7
appBar: PreferredSize(
preferredSize: const Size.fromHeight(40),
child: AppBar(
title: Text('控制 AppBar 的高度~'),
....
)
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import 'package:flutter/material.dart';

class MyAppBarDemo extends StatelessWidget {
const MyAppBarDemo({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColorDark: Colors.black26
),
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.cyan,
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {
print('菜单!');
},
),
title: Text('AppBar Demo!'),
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {
print('搜索!');
},
),
IconButton(
icon: Icon(Icons.more_horiz_outlined),
onPressed: () {
print('更多!');
},
),
],
),
),
);
}
}

TabBar 和 TabBarView 组件

属性

属性说明
tabs显示的标签内容,一般使用Tab对象,也可以是其他的Widget
controllerTabController对象
isScrollabletab 标签多了是否可滚动
indicatorColor指示器颜色
indicatorWeight指示器宽度
indicatorPadding底部指示器的Padding
indicator指示器decoration,例如边框等
indicatorSize指示器大小计算方式,TabBarIndicatorSize.label跟文字等宽TabBarIndicatorSize.tab跟每个tab等宽
labelColor选中label颜色
labelStyle选中label的Style
labelPadding每个label的padding值
unselectedLabelColor未选中label颜色
unselectedLabelStyle未选中label的Style

注意:

在实现功能前需要 with 混入下 with SingleTickerProviderStateMixin 这个类! 因为我们需要获取到_tabController tab 控制器!;
_tabController 这个控制器我们需要在 TabBar(controller: _tabController)TabBarView(controller: _tabController) 中当作参数应用!
tabBar 中的 tabs里的内容数量 要和 TabBarView 中的 children 里的数量保持一致!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import 'package:flutter/material.dart';

// 创建一个 无状态组件
class MyTabBarAppDemo extends StatefulWidget {
const MyTabBarAppDemo({super.key});

@override
State<MyTabBarAppDemo> createState() => _MyTabBarAppDemoState();
}

class _MyTabBarAppDemoState extends State<MyTabBarAppDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;

// 生命周期函数: 当组件加载完的时候就会触发
@override
void initState() {
// TODO: implement initState
super.initState();
// length: 9 这个长度 是用来控制 tabBar 和 tabView tab标签数量的!
// tabBar 中的 tabs 和 tabView 的页面数量一定要对应上
_tabController = TabController(length: 9, vsync: this);
}
@override
Widget build(BuildContext context) {
return MaterialApp(

);
}
}

组件什么时候销毁:

当触发页面切换,比如切换 BottomNavigationBar 底部 Tab 栏的时候 ,tabBarController 就会销毁!

main.dart

main.dart
1
2
3
4
import './tabBarAndTabView.dart';
void main(List<String> args) {
return runApp(const MyTabBarAppDemo());
}

tabBarAndTabView.dart

tabBarAndTabView.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import 'package:flutter/material.dart';

// 创建一个 无状态组件
class MyTabBarAppDemo extends StatefulWidget {
const MyTabBarAppDemo({super.key});

@override
State<MyTabBarAppDemo> createState() => _MyTabBarAppDemoState();
}

class _MyTabBarAppDemoState extends State<MyTabBarAppDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;

// 生命周期函数: 当组件加载完的时候就会触发
@override
void initState() {
// TODO: implement initState
super.initState();
// length: 9 这个长度 是用来控制 tabBar 和 tabView tab标签数量的!
// tabBar 中的 tabs 和 tabView 的页面数量一定要对应上
_tabController = TabController(length: 9, vsync: this);
// 监听 tab 事件
_tabController.addListener(() {
// 这里会打印两边,因为 切换 进入 和 离开的时候 都会各触发一次
print(_tabController.index);
// 处理方案
// animation!.value 值可能为空,加上 ! 感叹号 在不为空的时候获取
if (_tabController.animation!.value == _tabController.index) {
// 这里只会触发一次
print(_tabController.index);
}
});

@override
void dispose(){
super.dispose();
// 销毁 _tabController
_tabController.dispose();
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
leading: IconButton(icon: Icon(Icons.menu), onPressed: () {
print('click menu!');
},),
title: const Text('TabBar and TabView!'),
bottom: TabBar(
// 是否滚动
controller: _tabController,
isScrollable: true,
tabs: const [
Tab(
child: Text('关注'),
),
Tab(
child: Text('推荐'),
),
Tab(
child: Text('热榜'),
),
Tab(
child: Text('发现'),
),
Tab(
child: Text('抗疫'),
),
Tab(
child: Text('精品课'),
),
Tab(
child: Text('小说'),
),
Tab(
child: Text('微视频'),
),
Tab(
child: Text('娱乐'),
),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
ListView(
children: const [
ListTile(title: Text('关注Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('推荐Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('热榜Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('发现Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('抗疫Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('精品课Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('小说Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('微视频Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('娱乐Tab页面'),)
],
),

],
),
),
);
}
}

TabBarTabBarView 事件监听
第一种 利用 TabBar 中的 _tabController.addListener((){})

这里滑动点击 都可以监听到!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 生命周期函数: 当组件加载完的时候就会触发 
@override
void initState() {
// TODO: implement initState
super.initState();
// length: 9 这个长度 是用来控制 tabBar 和 tabView tab标签数量的!
// tabBar 中的 tabs 和 tabView 的页面数量一定要对应上
_tabController = TabController(length: 9, vsync: this);
// 监听 tab 事件
_tabController.addListener(() {
// 这里会打印两边,因为 切换 进入 和 离开的时候 都会各触发一次
print(_tabController.index);
// 处理方案
// animation!.value 值可能为空,加上 ! 感叹号 在不为空的时候获取
if (_tabController.animation!.value == _tabController.index) {
// 这里只会触发一次
print(_tabController.index);
}
});
}

第二种 在 TabBar 中 加入 onTab(){}事件

这里 只有在点击的时候会触发,不支持滑动触发!

1
2
3
4
5
TabBar(
onTab( index ){
// 只能监听点击事件 无法监听 滑动
}
)

KeepAliveWrapper 缓存界面

AutomaticKeepAliveClientMixin 可以快速的实现页面缓存功能,但是通过混入的方式实现不是很优雅,所以我们有必要对AutomaticKeepAliveClientMixin 混入进行封装
keepAlive : 是否缓存
child : 缓存的子组件
KeepAliveWrapper.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import 'package:flutter/material.dart';

class KeepAliveWrapper extends StatefulWidget {
const KeepAliveWrapper(
{Key? key, @required this.child, this.keepAlive = true})
: super(key: key);
final Widget? child;
final bool keepAlive;
@override
State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}

class _KeepAliveWrapperState extends State<KeepAliveWrapper>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
return widget.child!;
}

@override
bool get wantKeepAlive => widget.keepAlive;
@override
void didUpdateWidget(covariant KeepAliveWrapper oldWidget) {
if (oldWidget.keepAlive != widget.keepAlive) {
// keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中
updateKeepAlive();
}
super.didUpdateWidget(oldWidget);
}
}

头条顶部 AppBar 滑动案例

AppBar TabBar TabBarView 三种组件结合完成Demo实例!

widget tab(tabView.dart)

libs/widget/tabView.dart 封装 tabBar 和 tabBarView组件!

tabView.dart

注意:tabView.dart里引入KeepAliveWrapper.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import 'package:flutter/material.dart';
import '../tools/KeepAliveWrapper.dart';

class MyTabViewWidget extends StatefulWidget {
const MyTabViewWidget({super.key});

@override
State<MyTabViewWidget> createState() => _MyTabViewWidgetState();
}

class _MyTabViewWidgetState extends State<MyTabViewWidget> with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
// TODO: implement initState
super.initState();
_tabController = TabController(length: 9, vsync: this);
// 监听 tab 事件
_tabController.addListener(() {
// 这里会打印两边,因为 切换 进入 和 离开的时候 都会各触发一次
// print(_tabController.index);
// 处理方案
// animation!.value 值可能为空,加上 ! 感叹号 在不为空的时候获取
/* if (_tabController.animation!.value == _tabController.index) {
// 这里只会触发一次
print("监听切换tab ${_tabController.index}");
} */
if (!_tabController.indexIsChanging) {
print("监听切换tab ${_tabController.index} ");
}
});

@override
void dispose(){
super.dispose();
// 销毁 _tabController
print('dispose销毁');
_tabController.dispose();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(35),
child: AppBar(
elevation: 1,
backgroundColor: Colors.white,
title: SizedBox(
height: 35,
child: TabBar(
isScrollable: true,
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Colors.red,
labelColor: Colors.red,
unselectedLabelColor: Colors.black,
controller: _tabController,
tabs: const [
Tab(
child: Text('关注'),
),
Tab(
child: Text('推荐'),
),
Tab(
child: Text('热榜'),
),
Tab(
child: Text('发现'),
),
Tab(
child: Text('抗疫'),
),
Tab(
child: Text('精品课'),
),
Tab(
child: Text('小说'),
),
Tab(
child: Text('微视频'),
),
Tab(
child: Text('娱乐'),
),
],
),
),
),
),
body: TabBarView(
controller: _tabController,
children: [
// KeepAliveWrapper 缓存界面,当用户滚动到最下方 在切换到其他界面 在返回到最初界面时,保持最下方状态!
KeepAliveWrapper(
child: ListView(
children: const [
ListTile(title: Text('关注Tab页面1'),),
ListTile(title: Text('关注Tab页面2'),),
ListTile(title: Text('关注Tab页面3'),),
ListTile(title: Text('关注Tab页面4'),),
ListTile(title: Text('关注Tab页面5'),),
ListTile(title: Text('关注Tab页面6'),),
ListTile(title: Text('关注Tab页面7'),),
ListTile(title: Text('关注Tab页面8'),),
ListTile(title: Text('关注Tab页面9'),),
ListTile(title: Text('关注Tab页面10'),),
ListTile(title: Text('关注Tab页面11'),),
ListTile(title: Text('关注Tab页面12'),),
ListTile(title: Text('关注Tab页面13'),),
ListTile(title: Text('关注Tab页面14'),),
ListTile(title: Text('关注Tab页面15'),),
ListTile(title: Text('关注Tab页面16'),),
ListTile(title: Text('关注Tab页面17'),),
ListTile(title: Text('关注Tab页面18'),),
],
),
),
ListView(
children: const [
ListTile(title: Text('推荐Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('热榜Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('发现Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('抗疫Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('精品课Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('小说Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('微视频Tab页面'),)
],
),
ListView(
children: const [
ListTile(title: Text('娱乐Tab页面'),)
],
),

],
),
);
}
}

pages tab(home.dart,categroy.dart,message.dart,mine.dart,setting.dart)

lib/pages/tabs/ 新建这几个页面!

home.dart

注意:home.dart里引入tabView.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Home page
import 'package:flutter/material.dart';
import '../../widget/tabView.dart';

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {

late TabController _tabController;

@override
void initState() {
// TODO: implement initState
super.initState();
_tabController = TabController(length: 2, vsync: this);
}

@override
Widget build(BuildContext context) {
return const MyTabViewWidget();
}
}
categroy.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Categroy page
import 'package:flutter/material.dart';


class MyCategroyPage extends StatefulWidget {
const MyCategroyPage({super.key});

@override
State<MyCategroyPage> createState() => _MyCategroyPageState();
}

class _MyCategroyPageState extends State<MyCategroyPage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Categroy Page!', style: Theme.of(context).textTheme.headline5,),
);
}
}
message.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// message page
import 'package:flutter/material.dart';


class MyMessagePage extends StatefulWidget {
const MyMessagePage({super.key});

@override
State<MyMessagePage> createState() => _MyMessagePageState();
}

class _MyMessagePageState extends State<MyMessagePage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Message Page!', style: Theme.of(context).textTheme.headline5,),
);
}
}
mine.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// mine page
import 'package:flutter/material.dart';


class MyMinePage extends StatefulWidget {
const MyMinePage({super.key});

@override
State<MyMinePage> createState() => _MyMinePageState();
}

class _MyMinePageState extends State<MyMinePage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Mine Page!', style: Theme.of(context).textTheme.headline5,),
);
}
}
setting.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// setting page
import 'package:flutter/material.dart';


class MySettingPage extends StatefulWidget {
const MySettingPage({super.key});

@override
State<MySettingPage> createState() => _MySettingPageState();
}

class _MySettingPageState extends State<MySettingPage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Setting Page!', style: Theme.of(context).textTheme.headline5,),
);
}
}

lib/tools/创建 KeepAliveWrapper.dart

KeepAliveWrapper.dart 用来缓存用户界面状态的,比如: 用户在第一个界面浏览内容到最下方,这时候切换到别的界面,在切换会原页面,此时状态不会消失,应保持原有状态!(最初浏览到最下方的状态!)

KeepAliveWrapper.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import 'package:flutter/material.dart';

class KeepAliveWrapper extends StatefulWidget {
const KeepAliveWrapper(
{Key? key, @required this.child, this.keepAlive = true})
: super(key: key);
final Widget? child;
final bool keepAlive;
@override
State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}

class _KeepAliveWrapperState extends State<KeepAliveWrapper>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
return widget.child!;
}

@override
bool get wantKeepAlive => widget.keepAlive;
@override
void didUpdateWidget(covariant KeepAliveWrapper oldWidget) {
if (oldWidget.keepAlive != widget.keepAlive) {
// keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中
updateKeepAlive();
}
super.didUpdateWidget(oldWidget);
}
}

main.dart引入tabBarAndTabViewDemo.dart

main.dart
1
2
3
4
5
6
7
8
9
10
11
import 'package:flutter/material.dart';
import 'package:device_preview/device_preview.dart';

import './tabBarAndTabViewDemo.dart';

void main(List<String> args) {
return runApp(
DevicePreview(enabled: true , builder: ((context) => MyTabBarAppDemo()))
);
}

InkWell 点击组件

内部包含的组件 变为可点击效果!
可点击 长按 等触发事件效果!

1
2
3
4
5
6
InkWell(
child: Icon(Icons.close),
onTab(){
print('点我!');
}
)