Contents
  1. 1. Flutter 应用–Widget
    1. 1.1. Flutter常用Widgets
    2. 1.2. Text组件
    3. 1.3. TexField组件
    4. 1.4. 按钮(Button)组件
    5. 1.5. Dialog组件
    6. 1.6. Image组件
      1. 1.6.1. 加载本地图片
      2. 1.6.2. 加载网络图片
    7. 1.7. ListView组件
    8. 1.8. 小结

Flutter 应用–Widget

在Flutter的世界里,UI控件就是所谓的widget,通过组合不同的widget来实现我们的用户交互界面。

widget分为两种:StatelessWidgetStatefulWidget,我们先说说这个的区别

StatelessWidget:称为无状态组件,内部不保存状态界面不会发生改变。

StatefulWidget:称为有状态组件,内部保存状态界面会发生改变。

Flutter常用Widgets

在移动开发中,我们经常会跟按钮、文本输入框、图片等打交道,Flutter中也不例外,使用Flutter开发的App,界面上的每一个UI元素都是一个Widget,通过不同的Widget组合形成一整个页面。除了按钮、输入框、图片等Widget外,Flutter还给我们提供了很多功能强大,界面美观的Widget,看下面一段代码:

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

void main() => runApp(new TuyaApp());

class TuyaApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('Tuya Flutter Demo')
),
body: new Center(
child: new Text('Hello Tuya'),
),
),
);
}
}

在上面的代码是一个最简单的Hello Tuya程序,TuyaApp是我们自定义的一个类,它继承自StatelessWidget,代表它是一个无状态的组件,UI不会发生改变。build方法是父类的一个方法,被TuyaApp类重写了,继承自StatelessWidget的类必须实现TuyaApp方法并返回一个Widget对象。所以在上面的代码中,MaterialApp也是一个Widget,如果你用AndroidStudio查看源码,会发现MaterialApp的参数home也是一个Widget对象,所以上面的Scaffold也是一个Widget。

Text组件

Text组件是非常常用的组件,任何需要显示文本的地方基本都会用到。通过查看Text类的源码,可以发现Text是一个无状态的组件,下面的代码演示了如何修改Text组件的字号、颜色,给字体加粗、设置下划线、设置斜体等:

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';

void main() => runApp(new MaterialApp(
title: "Text Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Text Demo"),
),
body: new Center(
child: new Text(
"Hello Flutter",
style: new TextStyle(
color: Colors.red, // 或者用这种写法:const Color(0xFF6699FF) 必须使用AARRGGBB
fontSize: 20.0, // 字号
fontWeight: FontWeight.bold, // 字体加粗
fontStyle: FontStyle.italic, // 斜体
decoration: new TextDecoration.combine([TextDecoration.underline]) // 文本加下划线
),
),
),
),
));

运行效果如下:

text

TexField组件

TextFiled组件用于文本的输入,示例代码如下:

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';

void main() => runApp(new TuyaApp());
class TuyaApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Test",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test")
),
body: new Padding(
padding: const EdgeInsets.all(8.0),
child: new TextField(
maxLines: 8, // 设置输入框显示的最大行数(不是可输入的最大行数)
maxLength: 30, // 设置输入框中最多可输入的字符数
decoration: new InputDecoration( // 给输入框添加样式
hintText: "Input something...", // 输入框中placeholder文本
border: new OutlineInputBorder( // 输入框的边框
borderRadius: const BorderRadius.all(Radius.circular(1.0))
)
),
)
)
),
);
}
}

运行界面如下图:

text

按钮(Button)组件

Flutter提供了几种类型的按钮组件:RaisedButton FloatingActionButton FlatButton IconButton PopupMenuButton,下面用一段代码说明这几种按钮的用法:

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
import 'package:flutter/material.dart';

void main() => runApp(new TuyaApp());

enum WhyFarther { harder, smarter, selfStarter, tradingCharter }

class TuyaApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Button')
),
body: new Column(
children: <Widget>[
new RaisedButton(
child: new Text("Raised Button"),
onPressed: (){},
),
new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: (){},
),
new FlatButton(
onPressed: (){},
child: new Text("Flat Button")
),
new IconButton(
icon: new Icon(Icons.list),//flutter提供了很多按钮图片
onPressed: (){}
),
new PopupMenuButton<WhyFarther>(
onSelected: (WhyFarther result) {},
itemBuilder: (BuildContext context) => <PopupMenuEntry<WhyFarther>>[
const PopupMenuItem<WhyFarther>(
value: WhyFarther.harder,
child: const Text('第一个选项'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.smarter,
child: const Text('第二个选项'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.selfStarter,
child: const Text('第三个选项'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.tradingCharter,
child: const Text('第四个选项'),
),
],
)
],
)
)
);
}
}

运行效果如下图所示:

text

Dialog组件

Flutter提供了两种类型的对话框:SimpleDialog和AlertDialog。SimpleDialog是一个可以显示附加的提示或操作的简单对话框,AlertDialog则是一个会中断用户操作的对话框,需要用户确认的对话框,下面用代码来说明其用法:

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
import 'package:flutter/material.dart';

void main() => runApp(new TuyaApp());

class TuyaApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Test')
),
// body: new MyAlertDialogView()
body: new MySimpleDialogView(),
),
);
}
}

class MyAlertDialogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('显示AlertDialog'),
onPressed: () {
showDialog<Null>(
context: context,
barrierDismissible: false, // 不能点击对话框外关闭对话框,必须点击按钮关闭
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('提示'),
content: new Text('微软重申Windows 7将在2020年1月到达支持终点,公司希望利用这个机会说服用户在最新更新发布之前升级到Windows 10。'),
actions: <Widget>[
new FlatButton(
child: new Text('明白了'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
);
}
}

class MySimpleDialogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('显示SimpleDialog'),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext ctx) {
return new SimpleDialog(
title: new Text('这是SimpleDialog'),
children: <Widget>[
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text('确定'),
),
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text('取消'),
),
],
);
}
);
},
);
}
}

上面的代码分别展示了SimpleDialog和AlertDialog的基本用法。需要注意的是,这里并没有直接将按钮和显示对话框的逻辑写到TuyaApp类中,而是分两个StatelessWidget来写的,如果你直接将按钮及显示对话框的逻辑写到TuyaApp的build方法里,是会报错的,具体报错信息为:

1
2
Navigator operation requested with a context that does not include a Navigator.
复制代码

意思是导航操作需要一个不包含Navigator的上下文对象,而如果我们将showDialog的逻辑写到TuyaApp的build方法中时,使用的是MaterialApp的上下文对象,这个上下文对象是包含Navigator的,所以就会报错。

运行效果如下图:

text

text

Image组件

Image组件用于显示一张图片,可以加载本地(项目中或手机存储中)或网络图片。

加载本地图片

使用下面的方法加载一张项目中的图片:

1
new Image.asset(path, width: 20.0, height: 20.0, fit: BoxFit.cover)

其中path是项目中的图片目录。

加载项目中的图片一定要注意编辑pubspec.yaml文件:

假设当前我们在跟lib/同级的目录下创建了images/目录,在images/目录下存放了若干图片供项目使用,那么一定要记得在项目根目录下(也是跟images/同级的目录)编辑pubspec.yaml文件,打开pubspec.yaml文件,默认情况下assets是被注释了的,这里我们要取消注释assets并添加images/目录下的每个图片的路径,如下图所示:

image

在上图中我们配置了文件路径images/a_dot_burr.png,所以可以用下面的代码来加载图片了:

1
new Image.asset('images/a_dot_burr.png', width: 20.0, height: 20.0, fit: BoxFit.cover)

widthheight是图片长宽,为double类型,如果你传整型20则会报错。 如果要加载手机存储中的图片,使用下面的方法:

1
new Image.file(path, width: 20.0, height: 20.0, fit: BoxFit.cover)

fit属性指定了图片显示的不同方式,有如下几个值:

  • contain:尽可能大,同时仍然包含图片完全在目标容器内。
  • cover:尽可能小,同时仍然覆盖整个目标容器。
  • fill:通过拉伸图片的长宽比填充目标容器。
  • fitHeight:确保是否显示了图片的完整高度,而不管是否意图片高度溢出了目标容器。
  • fitWidth:确保是否显示了图片的完整宽度,而不管是否图片高度溢出目标容器。
  • none:对齐目标容器内的图片(默认情况下居中)并丢弃位于容器外的图片的任何部分。图片原始大小不会被调整。
  • scaleDown:对齐目标容器内的图片(默认情况下居中),如果必要的话,对图片进行缩放,以确保图片适合容器。这与contain的情况相同,否则它与没有一样。

加载网络图片

加载网络图片使用下面的方法:

1
new Image.network(imgUrl, width: 20.0, height: 20.0, fit: BoxFit.cover)

ListView组件

ListView组件用于显示一个列表,一个最简单的ListView可以用如下代码实现:

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

void main() {
List<Widget> items = new List();
for (var i = 0; i < 20; i++) {
items.add(new Text("List Item $i"));
}
runApp(new MaterialApp(
title: "Text Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Text Demo"),
),
body: new Center(
child: new ListView(children: items)
),
),
));
}

运行结果如下图所示:

listView

这样的ListView显示不是我们需要的,太难看,每个item没有边距而且没有分割线,所以我们用下面的代码改造一下:

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
import 'package:flutter/material.dart';

void main() {
// 装有ListView中所有item的集合
List<Widget> items = new List();
for (var i = 0; i < 20; i++) {
var text = new Text("ListView Item $i");
// Padding也是一个Widget,是一个有内边距的容器,可以装其他Widget
items.add(new Padding(
// 内边距设置为15.0,上下左右四边都是15.0
padding: const EdgeInsets.all(15.0),
// Padding容器中装的是Text组件
child: text
));
}
runApp(new MaterialApp(
title: "ListView",
home: new Scaffold(
appBar: new AppBar(
title: new Text("ListView"),
),
body: new Center(
// build是ListView提供的静态方法,用于创建ListView
child: new ListView.builder(
// itemCount是ListView的item个数,这里之所以是items.length * 2是因为将分割线也算进去了
itemCount: items.length * 2,
itemBuilder: (context, index) {
// 如果index为奇数,则返回分割线
if (index.isOdd) {
return new Divider(height: 1.0);
}
// 这里index为偶数,为了根据下标取items中的元素,需要对index做取整
index = index ~/ 2;
return items[index];
},
)
)
),
));
}

listView2

小结

关于Flutter的常用组件,我们就介绍到这,以上的示例代码可以直接运行在AndroidStudio或VSCode中看到效果。更多组件和用法可以在Flutter中文网中查看。