Flutter学习日记-log3

本文最后更新于:2 年前

Flutter中文网


日志随笔

一、复习

  • 命名路由
  • 路由传参

二、状态管理

  • Widget管理自身状态
  • Widget管理子Widget状态,在子Widget上包裹一层父Widget,子Widget把状态调整的方法对外提供成参数给父Widget
    • GestureDetector手势,onTap等方法
    • Container绘制矩形区域,decoration属性设置背景、边框等,decoration里面color字段和Container本身的color字段不能同时设置,会报错,使用padding调整位置,或者使用Center包裹居中
  • 混合管理状态
  • 全局状态管理,例如国际化等操作,此处暂不学习
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '状态管理',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: 'HomePage',
routes: {
'HomePage': (context) => HomePage(),
},
);
}
}

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
'这是首页',
style: TextStyle(
color: Colors.blue,
fontSize: 20,
),
),
TapboxA(),
ParentWidget(),
ParentWidgetC(),
],
),
),
);
}
}

// TapboxA 管理自身状态.

//------------------------- TapboxA ----------------------------------

class TapboxA extends StatefulWidget {
@override
_TapboxAState createState() => _TapboxAState();
}

class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_active;
});
}

Widget build(BuildContext context) {
return GestureDetector(
child: Container(
child: Center(
child: Text(
_active ? '00' : '99',
style: TextStyle(
fontSize: 100,
color: Colors.white,
backgroundColor: _active ? Colors.red : Colors.blue,
),
),
),
width: 200,
height: 200,
padding: EdgeInsets.only(left: 20),
decoration: BoxDecoration(
color: Colors.orange,
border: Border(
top: BorderSide(
color: Colors.black,
width: 10,
style: BorderStyle.solid,
)),
// border: Border.all(
// color: Colors.black,
// width: 10,
// style: BorderStyle.solid,
// ),
),
),
onTap: _handleTap,
);
}
}

// ParentWidget 为 TapboxB 管理状态.

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;

void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}

@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
final bool active;
final ValueChanged<bool> onChanged;

TapboxB({
Key key,
this.active: false,
@required this.onChanged
}): super(key: key);

void _handleTap() {
onChanged(!active);
}

Widget build(BuildContext context) {
return new GestureDetector(
onTap: _handleTap,
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],
),
),
);
}
}

//---------------------------- ParentWidget ----------------------------

class ParentWidgetC extends StatefulWidget {
@override
_ParentWidgetCState createState() => new _ParentWidgetCState();
}

class _ParentWidgetCState extends State<ParentWidgetC> {
bool _active = false;

void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}

@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxC(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}

//----------------------------- TapboxC ------------------------------

class TapboxC extends StatefulWidget {
TapboxC({Key key, this.active: false, @required this.onChanged})
: super(key: key);

final bool active;
final ValueChanged<bool> onChanged;

@override
_TapboxCState createState() => new _TapboxCState();
}

class _TapboxCState extends State<TapboxC> {
bool _highlight = false;

void _handleTapDown(TapDownDetails details) {
setState(() {
_highlight = true;
});
}

void _handleTapUp(TapUpDetails details) {
setState(() {
_highlight = false;
});
}

void _handleTapCancel() {
setState(() {
_highlight = false;
});
}

void _handleTap() {
widget.onChanged(!widget.active);
}

@override
Widget build(BuildContext context) {
// 在按下时添加绿色边框,当抬起时,取消高亮
return new GestureDetector(
onTapDown: _handleTapDown, // 处理按下事件
onTapUp: _handleTapUp, // 处理抬起事件
onTap: _handleTap,
onTapCancel: _handleTapCancel,
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,
),
),
);
}
}

三、组件和组件样式

1、文本

  • 基础样式:textAlignmaxLinesoverflowtextScaleFactor
  • 高级一点的直接定制TextStyle属性
  • TextSpan实现富文本样式
  • DefaultTextStyle样式继承
  • 字体:使用Google Fonts中的字体,首先在pubspec.yaml中声明,然后通过TextStyle属性使用

2、按钮

此处学习基于Android端的Material组件,所有Material 库中的按钮都是直接或间接的RawMaterialButton组件的包装定制,都有如下相同点:

  1. 按下时都会有“水波动画”(又称“涟漪动画”,就是点击时按钮上会出现水波荡漾的动画)
  2. 有一个onPressed属性来设置点击回调,当按钮按下时会执行该回调,如果不提供该回调则按钮会处于禁用状态,禁用状态不响应用户点击
  • RaisedButton漂浮按钮,文字+图标
  • FlatButton扁平按钮,文字+图标
    • highlightColorsplashColor冲突,前者设置后,后者的效果就没了,暂不研究
    • shape属性是ShapeBorder类的,但ShapeBorder不直接使用,应该使用该类的子类,子类提供了具体的样式效果
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const FlatButton({
      ...
      @required this.onPressed, //按钮点击回调
      this.textColor, //按钮文字颜色
      this.disabledTextColor, //按钮禁用时的文字颜色
      this.color, //按钮背景颜色
      this.disabledColor,//按钮禁用时的背景颜色
      this.highlightColor, //按钮按下时的背景颜色
      this.splashColor, //点击时,水波动画中水波的颜色
      this.colorBrightness,//按钮主题,默认是浅色主题
      this.padding, //按钮的填充
      this.shape, //外形
      @required this.child, //按钮的内容
      })
  • OutlineButton边框按钮,文字+图标
  • IconButton图标按钮,没有文字
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
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '控件样式',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: 'HomePage',
routes: {
'HomePage': (context) => HomePage(),
},
);
}
}

class HomePage extends StatelessWidget {
void _onPressed() {}

@override
Widget build(BuildContext context) {
String icons = "";
// accessible: &#xE914; or 0xE914 or E914
icons += "\uE914";
// error: &#xE000; or 0xE000 or E000
icons += " \uE000";
// fingerprint: &#xE90D; or 0xE90D or E90D
icons += " \uE90D";

return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
RaisedButton(
child: Text("RaisedButton"),
onPressed: () {},
),
FlatButton(
child: Text("FlatButton"),
onPressed: () {},
),
OutlineButton(
child: Text("normal"),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.thumb_up),
onPressed: () {},
),
],
),
),
Container(
child: Row(
children: <Widget>[
RaisedButton.icon(
icon: Icon(Icons.send),
label: Text("发送"),
onPressed: _onPressed,
),
FlatButton.icon(
icon: Icon(Icons.info),
label: Text("详情"),
onPressed: _onPressed,
),
OutlineButton.icon(
icon: Icon(Icons.add),
label: Text("添加"),
onPressed: _onPressed,
),
],
),
),
Container(
child: Row(
children: <Widget>[
FlatButton(
onPressed: _onPressed,
child: Text("提交"),
color: Colors.blue,
textColor: Colors.white,
// highlightColor: Colors.blue[700],
// splashColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
),
RaisedButton(
onPressed: _onPressed,
child: Text("提交"),
color: Colors.blue,
textColor: Colors.white,
// highlightColor: Colors.blue[700],
// splashColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
),
],
),
),
],
),
);
}
}

3、图片和图标

1. 图片
  • ImageProvider是一个抽象类,从不同的数据源获取图片使用不同的方法,从Asset中加载图片使用AssetImage,从网络加载图片使用NetworkImage
  • fit属性
    • fill:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。
    • cover:会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示空间部分会被剪裁。
    • contain:这是图片的默认适应规则,图片会在保证图片本身长宽比不变的情况下缩放以适应当前显示空间,图片不会变形。
    • fitWidth:图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
    • fitHeight:图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
    • none:图片没有适应策略,会在显示空间内显示图片,如果图片比显示空间大,则显示空间只会显示图片中间部分。

image_1

  • Flutter框架对加载过的图片是有缓存的(内存),默认最大缓存数量是1000,最大缓存空间为100M,此处暂不学习
2. 图标

Flutter默认包含了一套Material Design的字体图标iconfontpubspec.yaml文件中配置默认打开,官网地址:https://material.io/tools/icons/

4、单选和复选框

  • Switch
  • Checkbox

5、输入框

  • onChange方法通知输入框文本发生变化
  • 创建TextEditingController控制器,绑定输入框
    • 设置占位文本,比输入框本身的属性优先级高
    • 获取绑定的输入框内的文本
    • 创建FocusNode,绑定输入框,来控制键盘焦点

6、Form表单

Form表单是基于输入框封装的一个功能更完善的控件,需要对应TextFormField的使用,可以实现validate方法对所有输入框条件的一键验证

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '表单',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FormTestRoute(),
);
}
}

class FormTestRoute extends StatefulWidget {
@override
_FormTestRouteState createState() => new _FormTestRouteState();
}

class _FormTestRouteState extends State<FormTestRoute> {
TextEditingController _unameController = new TextEditingController();
TextEditingController _pwdController = new TextEditingController();
GlobalKey _formKey = new GlobalKey<FormState>();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Form Test"),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Form(
key: _formKey, //设置globalKey,用于后面获取FormState
autovalidate: true, //开启自动校验
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
controller: _unameController,
decoration: InputDecoration(
labelText: "用户名",
hintText: "用户名或邮箱",
icon: Icon(Icons.person)),
// 校验用户名
validator: (v) {
return v.trim().length > 0 ? null : "用户名不能为空";
}),
TextFormField(
controller: _pwdController,
decoration: InputDecoration(
labelText: "密码",
hintText: "您的登录密码",
icon: Icon(Icons.lock)),
obscureText: true,
//校验密码
validator: (v) {
return v.trim().length > 5 ? null : "密码不能少于6位";
}),
// 登录按钮
Padding(
padding: const EdgeInsets.only(top: 28.0),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
padding: EdgeInsets.all(15.0),
child: Text("登录"),
color: Theme.of(context).primaryColor,
textColor: Colors.white,
onPressed: () {
//在这里不能通过此方式获取FormState,context不对
//print(Form.of(context));

// 通过_formKey.currentState 获取FormState后,
// 调用validate()方法校验用户名密码是否合法,校验
// 通过后再提交数据。
if ((_formKey.currentState as FormState).validate()) {
//验证通过提交数据
debugPrint('通过');
}
},
),
),
],
),
)
],
),
),
),
);
}
}

7、进度条指示器

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '进度条控件',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController _animationController;

@override
void initState() {
//动画执行时间3秒
_animationController =
new AnimationController(vsync: this, duration: Duration(seconds: 3));
_animationController.forward();
_animationController.addListener(() => setState(() => {}));
super.initState();
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('主页'),
),
body: Column(
children: <Widget>[
LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
//进度条显示50%
LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .5,
),
// 模糊进度条(会执行一个旋转动画)
CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
//进度条显示50%,会显示一个半圆
CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .5,
),
// 线性进度条高度指定为3
SizedBox(
height: 3,
child: LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .5,
),
),
// 圆形进度条直径指定为100
SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .7,
),
),
// 宽高不等
SizedBox(
height: 100,
width: 130,
child: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .7,
),
),
LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: ColorTween(begin: Colors.grey, end: Colors.blue)
.animate(_animationController), // 从灰色变成蓝色
value: _animationController.value,
),
],
),
);
}
}

四、布局

1、线性布局(RowColumn

  • RowColumn会在主轴方向占用尽可能大的空间,而纵轴的长度则取决于他们最大子元素的长度
  • 如果Row里面嵌套Row,或者Column里面再嵌套Column,那么只有最外面的RowColumn会占用尽可能大的空间,里面RowColumn所占用的空间为实际大小

2、弹性布局(Flex

  • RowColumn都继承自Flex
  • Expanded可以按比例“扩伸”RowColumnFlex子组件所占用的空间
  • Spacer的功能是占用指定比例的空间

3、流式布局(WrapFlow

  • 超出屏幕显示范围会自动折行的布局称为流式布局
  • 一般很少会使用Flow

4、层叠布局(StackPositioned

  • Stack允许子组件堆叠
  • Positioned用于根据Stack的四个角来确定子组件的位置
  • StackPositioned最好一起搭配使用

5、对齐与相对定位(Align

1. Alignment
  • Alignment会以矩形的中心点作为坐标原点,即Alignment(0.0, 0.0)xy的值从-11分别代表矩形左边到右边的距离和顶部到底边的距离
  • 计算公式:(Alignment.x*childWidth/2+childWidth/2, Alignment.y*childHeight/2+childHeight/2)
2. FractionalOffset
  • FractionalOffset继承自Alignment,它和Alignment唯一的区别就是坐标原点不同,FractionalOffset的坐标原点为矩形的左侧顶点
  • 计算公式:实际偏移 = (FractionalOffse.x * childWidth, FractionalOffse.y * childHeight)
3. AlignStack对比
  • 定位参考系统不同
  • Stack可以有多个子元素,并且子元素可以堆叠,而Align只能有一个子元素,不存在堆叠
  • Center继承自Align,可以认为Center组件其实是对齐方式确定(Alignment.center)了的Align

未完待续