widget
Flutter 几乎所有对象都是Widget。不过实际上Widget是“描述一个UI元素的配置数据”,真正显示在屏幕上的元素是Element。
- Widget实际上是Element的配置数据,Widget数实际上就是一个配置树,真正的UI渲染有element构成。
- 一个Widget对象可以对应多个Element对象。
Widget本身是一个抽象类。一般通过继承StatelessWidget和StatefulWidget两个类来间接继承widget类实现。上一节有说过,StatelessWidget和StatefulWidget的区别也就是有无状态的区别。
StatelessWidget
StatelessWidget相对简单一点,因其不需要维护状态,所以只需在build方法中嵌套其他widget来构建UI就可以。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
32class Echo entends StatelessWidget {
//构造函数
const Echo({
Key key,
this.text, //@required 必要参数
this.backgroundColor: UIColor.grey,
}):super(key:key);
//变量声明
final String text;
final Color backgroundColor;
//build
Widget build(BuildContext context) {
return Center(
child:Container(
color: backgroundColor,
child: Text(text),
),
);
}
}
//使用
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children:<Widget>[
Text("This is a new route"),
Echo(text: "hello world"),
],
),
StatefulWidget
与StatelessWidget不同的是,StatefulWidget需要维护一个State。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
74class CounterWidget extends StatefulWidget {
const CounterWidget({
Key key,
this.initValue: 0
});
final int initValue;
_CounterWidgetState createState() => new _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter;
// 初始化只会被调用1次,
void initState() {
super.initState();
// 初始化状态
_counter = widget.initValue;
print("initState");
}
//主要用来构建widget子树的。1、在initState()会被调用;2、在didUpdateWidget()会被调用;3、在setState()之后会被调用;4、在didChangeDependencies()会被调用
Widget build(BuildContext context) {
print("build");
return Center(
child: FlatButton(
child: Text('$_counter'),
//点击后 计数器自增
onPressed: ()=>setState(()=> ++_counter),
),
);
}
//更新widget
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
//State对象被移动时
@override
void deactivate() {
super.deactivate();
print("deactivate");
}
//State对象被永久移除时
@override
void dispose() {
super.dispose();
print("dispose");
}
//热重载时会被调用 主要用来debug时调试使用,release下不会执行
@override
void reassemble() {
// TODO: implement reassemble
super.reassemble();
print("reassemble");
}
// 当State对象的依赖发生变化时会被调用
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
print("didChangeDependencies");
}
}
状态管理
stateful Widget的状态由谁管理?
常见的管理方法:
- Widget 管理自己的state;
- 父widget管理子widget的状态;
- 混合管理(父widget和子widget都管理状态)
- 全局状态管理:如果应用里有一些跨widget或路由的状态需要同步时,则需要使用全局状态管理。
如何决定使用哪种管理方法,有以下原则:
- 如果状态是用户数据,如复选框的选中状态、滑块位置,则最好由父widget管理;
- 如果状态是外观效果,例如颜色动画,则最好由widget本身管理;
- 如果某一状态是不同widget共享的,则最好由它们的共同父widget管理。
基本的原理实际上是:通过setState来更新某个变量,当调用setState的时候就会触发build函数,然后在函数内部实现根据变量的不同值进行对应的变化的代码。
父类管理的话,实际就是通过一个block回调,将当前变化返回给父widget,父widget接收到变化后,对子widget执行setState函数,以调用子widget的build。达到管理效果。
来分别感受一下这些代码的区别:
1、widget管理自己的State1
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// 自己管理状态
class StateBoxA extends StatefulWidget {
StateBoxA({Key key}):super(key:key);
_StateBoxA createState() => _StateBoxA();
}
class _StateBoxA extends State<StateBoxA> {
bool _active = false;
Widget build(BuildContext context) {
// TODO: implement build
return new GestureDetector(
onTap: (){
setState(() {
_active = !_active;
});
},
child: new Container(
child: new Center(
child: new Text(
_active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0,color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: _active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
2、父widget管理State1
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// 父类管理状态
class ParentWidget extends StatefulWidget {
_ParentWidgetState createState() => new _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
Widget build(BuildContext context) {
// TODO: implement build
return new Container(
child: new StateBoxB(
active: _active,
onChanged: (bool newValue){
setState(() {
_active = newValue;
});
},
),
);
}
}
class StateBoxB extends StatelessWidget {
const StateBoxB({
Key key,
this.active: false,
this.onChanged}
) :super(key:key);
final bool active;
final ValueChanged<bool> onChanged;
Widget build(BuildContext context) {
// TODO: implement build
return new GestureDetector(
onTap: (){
onChanged(!active);
},
child: new Container(
child: new Center(
child: new Text(
active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0,color: Colors.white),
),
),
width: 200.0,
height:200.0,
decoration: new BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
3、混合管理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//混合管理
class ParentWidgetC extends StatefulWidget {
_ParentWidgetCState createState() => new _ParentWidgetCState();
}
class _ParentWidgetCState extends State<ParentWidgetC> {
bool _active = false;
Widget build(BuildContext context) {
// TODO: implement build
return new Container(
child: new TapboxC(
active: _active,
onChanged: (newValue){
setState(() {
_active = newValue;
});
},
),
);
}
}
class TapboxC extends StatefulWidget {
TapboxC({
Key key,
this.active: false,
this.onChanged,
}):super(key:key);
final bool active;
final ValueChanged<bool> onChanged;
_TapboxCState createState() => new _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
bool _highlight = false;
Widget build(BuildContext context) {
// 在按下时添加绿色边框,抬起时,取消高亮
return new GestureDetector(
onTapDown: (TapDownDetails details){
setState(() {
_highlight = true;
});
},//处理按下事件
onTapUp: (TapUpDetails details){
setState(() {
_highlight = false;
});
},//处理抬起事件
onTap: (){
widget.onChanged(!widget.active);//State对应的widget,调用onchanged
}, // 处理tap事件
onTapCancel: (){
setState(() {
_highlight = false;
});
},//取消点击
child: new Container(
child: new Center(
child: new Text(widget.active ? 'Active' : 'Inactive', style: new TextStyle(fontSize: 32.0,color: Colors.white),),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight ? new Border.all(
color: Colors.teal[700],
width: 10.0,
)
: null,
),
),
);
}
}
基础Widgets
文本、字体样式
1.1 Text。TextStyle用于设置Text的样式属性,比如颜色、字体、高度、换行方式等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Text('Hello world',textAlign: TextAlign.center,),//对其方式,参照系是widget本身
Text('Hello world! Im fcf! '*4, //字符串重复4次
maxLines: 1, //最多1行
overflow: TextOverflow.ellipsis, //显示不全的截断方式
),
Text('Hello world', textScaleFactor: 1.5,),//相对于当前字体缩放,默认是1.0
Text('Fuck',style: TextStyle(
color: Colors.blue,
fontSize: 32.0,
height: 1.2,
fontFamily: 'Courier',
background: new Paint()..color = Colors.yellow,
decoration: TextDecoration.underline, //装饰,比如下划线
decorationStyle: TextDecorationStyle.dashed //下划线样式
),),1.2 Rich TextSpan 富文本。TextSpan实际上是代表一个文本片段,实际上也相当于原生中的富文本的效果。
1
2
3
4
5
6
7
8
9
10
11
12Text.rich(TextSpan(
children: [
TextSpan(text: 'Baidu '),
TextSpan(
text: 'www.baidu.com',
style: TextStyle(
color: Colors.red,
),
recognizer: _tapRecognizer,//点击后续
),
]
))2、书写默认文本样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20DefaultTextStyle(
//设置文本默认样式
style: TextStyle(
color: Colors.red,
fontSize: 20.0,
),
textAlign: TextAlign.start,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('You '),
Text('are '),
Text('fool ',
style: TextStyle(
inherit: false,//不继承样式
color: Colors.grey,
),),
],
),
)3、字体
要使用非默认字体,则首先要在pubspec.yaml中声明,然后在TextStyle使用。
1、将字体文件打包到应用中,然后在pubspec.yaml中声明路径。
2、使用字体时。在TextStyle中使用fontFamily。如果是在package包中,则也要在package中指定包名。1
2
3
4const textStyle = const TextStyle(
fontFamily: 'Raleway',
package: 'my_package', //指定包名
);
按钮
Material提供了多种按钮widget。RaisedButton、FlatButton、OutlineButton、IconButton
1 | //漂浮按钮,默认带有阴影和背景色 |
图片及ICON
Flutter通过Image加载并显示图片,Image的数据源可以是asset、文件、内存及网络。1
2
3
4
5
6
7
8
9
10//本地资源图
Image(
image: AssetImage("assets/1.jpg"),
width: 100.0,
),
//网络资源图
Image(
image: NetworkImage('https://avatars2.githubusercontent.com/u/20411648?s=460&v=4'),
width: 100.0,
),
Image 的宽高,如果不指定宽或者高,则图片会根据当前父容器,尽可能的显示其原始大小。如果只设置了width、height的其中一个,那么另一个会默认按比例缩放。
Image的缩放模式fit:
- fill :会拉伸填充显示空间,图片本身长宽比会发生变化,图片会变形;
- cover :会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示空间部分会被裁剪;
- contain :默认填充,图片会保证图片本身长宽比不变的情况下缩放以适应当前显示空间,图片不会变形。
- fitWidth :图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出部分会被裁剪。
- fitHeight :图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出部分会被裁剪。
- none :会在图片空间内显示图片,如果图片比空间大,则显示空间只会显示图片中间部分。
Image的颜色,有一个colorBlendMode,颜色混合模式
IconFont 字体图标:它是将图标做成字体文件,然后通过指定不同的字符而显示不同的图片。
在pubspec.yaml中 ‘uses-material-design: true‘ 即表示使用material design的字体图标1
2
3
4
5
6
7
8
9
10
11//通过码点实现图标
Text("\uE914",style: TextStyle(fontFamily: 'MaterialIcons',fontSize: 24.0,color: Colors.green),),
//通过flutter封装的IconData和Icon显示字体图标
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.accessible,color: Colors.green,),
Icon(Icons.error,color: Colors.red,),
Icon(Icons.fingerprint,color: Colors.blue,),
],
)
也可以通过使用ttf格式的文件自定义字体图标。
单选开关Switch和复选框checkBox
Switch和CheckBox的状态都是要由父widget管理。所以正常来说应该是在StatefulWidget中使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Switch(
value: _switchSelected, //当前状态
onChanged: (value) {
setState(() {
_switchSelected = value;
});
},
),
Checkbox(
value: _checkboxSelected, //当前状态
activeColor: Colors.red,
onChanged: (value) {
setState(() {
_checkboxSelected = value;
});
},
),
Switch和Checkbox都有一个activeColor用于设置激活状态下的颜色,Checkbox的大小是固定的,Switch也只能定义宽度,高度也是固定的。
输入框及表单
TextField 这是一个很复杂的widget,首先有个TextEditingController属性用于获取输入的内容,其次需要有FocusNode或FocusScopeNode来控制焦点,比如点击空白页让键盘收起等操作。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 TextEditingController _unameController = new TextEditingController();
TextEditingController _upwdController = new TextEditingController();
FocusNode namefocusNode = new FocusNode();
FocusNode pwdfocusNode = new FocusNode();
FocusScopeNode focusScopeNode; //管理焦点
//给controller添加监听,也可以直接给TextField添加onChanged回调。
void initState(){
//监听
_unameController.addListener((){
print(_unameController.text);
});
_upwdController.addListener((){
print(_upwdController.text);
});
}
TextField(
autofocus: true,
controller: _unameController,
decoration: InputDecoration(
labelText: "用户名:",
hintText: "请填写正确用户名",
prefixIcon: Icon(Icons.person)
),
),
TextField(
controller: _upwdController,
decoration: InputDecoration(
labelText: "密码:",
hintText: "请填写密码",
prefixIcon: Icon(Icons.lock)
),
obscureText: true, //是否密码
),
//将焦点移动到密码
onPressed: () {
if(null == focusScopeNode){
focusScopeNode = FocusScope.of(context);
}
focusScopeNode.requestFocus(pwdfocusNode);
},
//隐藏键盘,失去焦点
onPressed: (){
namefocusNode.unfocus();
pwdfocusNode.unfocus();
},