你会学到什么
如何响应点击。
如何创建自定义 widget。
无状态和有状态 widget 之间的区别。
如何修改您的应用程序以使其对用户输入做出反应?在本教程中,您将为仅包含非交互式 widget 的应用程序添加交互性。具体来说,您将通过创建一个管理两个无状态 widget 的自定义有状态 widget,修改一个图标实现使其可点击。
中展示了如何构建下面截图所示的布局。
the layout tutorial app
Icon
、
IconButton
和
Text
都是无状态 widget,它们都是
StatelessWidg
et
的子类。
Checkbox
、
Radio
、
Slider
、
InkWell
、
Form
和
TextField
都是有状态 widget,它们都是
StatefulWidget
的子类。
State
对象中, 它和 widget 的显示分离。 Widget 的状态是一些可以更改的值, 如一个滑动条的当前值或一个复选框是否被选中。 当 widget 状态改变时, State 对象调用
setState()
, 告诉框架去重绘 widget。
重点是什么?
-
实现一个有状态 widget 需要创建两个类:一个
StatefulWidget
的子类和一个State
的子类。 -
State 类包含该 widget 的可变状态并定义该 widget 的
build()
方法. -
当 widget 状态改变时, State 对象调用
setState()
, 告诉框架去重绘 widget。
IconButton
和
Text
。
-
一个 StatefulWidget 的子类,用来定义一个 widget 类。
-
一个
State
的子类,包含该widget状态并定义该 widget 的build()
方法.
FavoriteWidget
的 StatefulWidget。 第一步是选择如何管理
FavoriteWidget
的状态。
1. 确保你已经设置好了你的环境(
3. 用 GitHub 上的 main.dart 替换 lib/main.dart
文件。
main.dart 链接:https://github.com/cfug/flutter.cn/blob/master/examples/layout/lakes/step6/lib/main.dart
4. 用 GitHub 上的 pubspec.yaml 替换 pubspec.yaml
文件。
pubspec.yaml 链接:https://github.com/cfug/flutter.cn/blob/master/examples/layout/lakes/step6/pubspec.yaml
5. 在你的工程中创建一个 images
文件夹, 并添加 lake.jpg。
lake.jpg 链接:https://github.com/cfug/flutter.cn/blob/master/examples/layout/lakes/step6/images/lake.jpg
FavoriteWidget
,将管理自己的状态。 在这个例子中,切换星形图标是一个独立的操作,不会影响父窗口 widget 或其他用户界面,因此该 widget 可以在内部处理它自己的状态。
FavoriteWidget
类管理自己的状态,因此它通过重写
createState()
来创建状态对象。 框架会在构建 widget 时调用
createState()
。 在这个例子中,
createState()
创建
_FavoriteWidgetState
的实例,您将在下一步中实现该实例。
1class FavoriteWidget extends StatefulWidget {
2 @override
3 _FavoriteWidgetState createState() => _FavoriteWidgetState();
4}
_
)开头的成员或类是私有的。 有关更多信息,请参阅 Dart language tour 中的 Libraries and visibility 部分。
步骤4: 创建 State 的子类
_FavoriteWidgetState
类存储可变信息;可以在 widget 的生命周期内改变逻辑和内部状态。当应用第一次启动时,用户界面显示一个红色实心的星星形图标,表明该湖已经被收藏,并有 41 个“喜欢”。状态对象存储这些信息在 _isFavorited
和 _favoriteCount
变量中。
1class _FavoriteWidgetState extends State<FavoriteWidget> {
2 bool _isFavorited = true;
3 int _favoriteCount = 41;
4 // ···
5}
build()
方法。 这个
build()
方法创建一个包含红色
IconButton
和
Text
的行。 该 widget 使用 IconButton (而不是
Icon
),因为它具有一个
onPressed
属性,该属性定义了处理点击的回调方法(
_toggleFavorite
)。 你将会在接下来的步骤中尝试定义它。
1class _FavoriteWidgetState extends State<FavoriteWidget> {
2 // ···
3 @override
4 Widget build(BuildContext context) {
5 return Row(
6 mainAxisSize: MainAxisSize.min,
7 children: [
8 Container(
9 padding: EdgeInsets.all(0),
10 child: IconButton(
11 icon: (_isFavorited ? Icon(Icons.star) : Icon(Icons.star_border)),
12 color: Colors.red[500],
13 onPressed: _toggleFavorite,
14 ),
15 ),
16 SizedBox(
17 width: 18,
18 child: Container(
19 child: Text('$_favoriteCount'),
20 ),
21 ),
22 ],
23 );
24 }
25}
Text
in a SizedBox and setting its width prevents a discernible “jump” when the text changes between the values of 40 and 41 — a jump would otherwise occur because those values have different widths.
Text
在 40 和 41 之间变化时,将文本放在 SizedBox 中并设置其宽度可防止出现明显的“跳跃”,因为这些值具有不同的宽度。
IconButton
时会调用
_toggleFavorite()
方法,然后它会调用
setState()
。 调用
setState()
是至关重要的,因为这告诉框架,widget 的状态已经改变,应该重绘。
setState()
在如下两种状态中切换 UI:
实心的星形图标和数字 ‘41’
轮廓线的星形图标和数字 ‘40’ 之间切换 UI
1void _toggleFavorite() {
2 setState(() {
3 if (_isFavorited) {
4 _favoriteCount -= 1;
5 _isFavorited = false;
6 } else {
7 _favoriteCount += 1;
8 _isFavorited = true;
9 }
10 });
11}
build()
方法中添加到 widget 树中。 首先,找到创建
图标
和
文本
的代码,并删除它,在相同的位置创建 stateful widget:
-
lib/main.dart
-
pubspec.yaml
-
lakes.jpg
重点是什么?
-
有多种方法可以管理状态。
-
您作为 widget 的设计者,需要选择使用何种管理方法。
-
如果不是很清楚时, 就在父 widget 中管理状态。
-
widget 管理自己的状态
-
父 widget 管理此 widget 的状态
-
混搭管理
-
如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 widget 管理。
-
如果所讨论的状态是有关界面外观效果的,例如动画,那么状态最好由 widget 本身来管理。
_active
确定颜色: 绿色为 true,灰色为 false。
_TapboxAState
类:
-
管理
TapboxA
的状态. -
定义布尔值
_active
确定盒子的当前颜色. -
定义
_handleTap()
函数,该函数在点击该盒子时更新_active
,并调用setState()
更新 UI。 -
实现 widget 的所有交互式行为。
1// TapboxA manages its own state.
2
3// TapboxA 管理自身状态.
4
5//------------------------- TapboxA ----------------------------------
6
7class TapboxA extends StatefulWidget {
8 TapboxA({Key key}) : super(key: key);
9
10 @override
11 _TapboxAState createState() => _TapboxAState();
12}
13
14class _TapboxAState extends State<TapboxA> {
15 bool _active = false;
16
17 void _handleTap() {
18 setState(() {
19 _active = !_active;
20 });
21 }
22
23 Widget build(BuildContext context) {
24 return GestureDetector(
25 onTap: _handleTap,
26 child: Container(
27 child: Center(
28 child: Text(
29 _active ? 'Active' : 'Inactive',
30 style: TextStyle(fontSize: 32.0, color: Colors.white),
31 ),
32 ),
33 width: 200.0,
34 height: 200.0,
35 decoration: BoxDecoration(
36 color: _active ? Colors.lightGreen[700] : Colors.grey[600],
37 ),
38 ),
39 );
40 }
41}
42
43//------------------------- MyApp ----------------------------------
44
45class MyApp extends StatelessWidget {
46 @override
47 Widget build(BuildContext context) {
48 return MaterialApp(
49 title: 'Flutter Demo',
50 home: Scaffold(
51 appBar: AppBar(
52 title: Text('Flutter Demo'),
53 ),
54 body: Center(
55 child: TapboxA(),
56 ),
57 ),
58 );
59 }
60}
-
为 TapboxB 管理
_active
状态. -
实现
_handleTapboxChanged()
,当盒子被点击时调用的方法. -
当状态改变时,调用
setState()
更新 UI.
-
继承 StatelessWidget 类,因为所有状态都由其父 widget 处理.
-
当检测到点击时,它会通知父 widget。
1// ParentWidget manages the state for TapboxB.
2
3// ParentWidget 为 TapboxB 管理状态.
4
5//------------------------ ParentWidget --------------------------------
6
7class ParentWidget extends StatefulWidget {
8 @override
9 _ParentWidgetState createState() => _ParentWidgetState();
10}
11
12class _ParentWidgetState extends State<ParentWidget> {
13 bool _active = false;
14
15 void _handleTapboxChanged(bool newValue) {
16 setState(() {
17 _active = newValue;
18 });
19 }
20
21 @override
22 Widget build(BuildContext context) {
23 return Container(
24 child: TapboxB(
25 active: _active,
26 onChanged: _handleTapboxChanged,
27 ),
28 );
29 }
30}
31
32//------------------------- TapboxB ----------------------------------
33
34class TapboxB extends StatelessWidget {
35 TapboxB({Key key, this.active: false, @required this.onChanged})
36 : super(key: key);
37
38 final bool active;
39 final ValueChanged<bool> onChanged;
40
41 void _handleTap() {
42 onChanged(!active);
43 }
44
45 Widget build(BuildContext context) {
46 return GestureDetector(
47 onTap: _handleTap,
48 child: Container(
49 child: Center(
50 child: Text(
51 active ? 'Active' : 'Inactive',
52 style: TextStyle(fontSize: 32.0, color: Colors.white),
53 ),
54 ),
55 width: 200.0,
56 height: 200.0,
57 decoration: BoxDecoration(
58 color: active ? Colors.lightGreen[700] : Colors.grey[600],
59 ),
60 ),
61 );
62 }
63}
@required
为代码所依赖的任何参数使用注解。 要使用
@required
注解,请导入 foundation library (该库重新导出 Dart 的 meta.dart ):
1 import 'package:flutter/foundation.dart';
3.3 混搭管理
对于一些 widget 来说,混搭管理的方法最合适的。在这种情况下,有状态的 widget 自己管理一些状态,同时父 widget 管理其他方面的状态。
在 TapboxC
示例中,点击时,盒子的周围会出现一个深绿色的边框。点击时,边框消失,盒子的颜色改变。TapboxC
将其 _active
状态导出到其父 widget 中,但在内部管理其 _highlight
状态。这个例子有两个状态对象 _ParentWidgetState
和 _TapboxCState
。
_ParentWidgetState
对象:
-
管理
_active
状态。 -
实现
_handleTapboxChanged()
, 此方法在盒子被点击时调用。 -
当点击盒子并且
_active
状态改变时调用setState()
来更新UI。
_TapboxCState
对象:
-
管理
_highlight
state。 -
GestureDetector
监听所有 tap 事件。 当用户点下时,它添加高亮(深绿色边框); 当用户释放时,会移除高亮。 -
当按下、抬起、或者取消点击时更新
_highlight
状态,调用setState()
更新UI。 -
当点击时, widget 属性将状态的改变传递给父 widget 并进行合适的操作。
1 //---------------------------- ParentWidget ----------------------------
2
3class ParentWidget extends StatefulWidget {
4 @override
5 _ParentWidgetState createState() => _ParentWidgetState();
6}
7
8class _ParentWidgetState extends State<ParentWidget> {
9 bool _active = false;
10
11 void _handleTapboxChanged(bool newValue) {
12 setState(() {
13 _active = newValue;
14 });
15 }
16
17 @override
18 Widget build(BuildContext context) {
19 return Container(
20 child: TapboxC(
21 active: _active,
22 onChanged: _handleTapboxChanged,
23 ),
24 );
25 }
26}
27
28//----------------------------- TapboxC ------------------------------
29
30class TapboxC extends StatefulWidget {
31 TapboxC({Key key, this.active: false, @required this.onChanged})
32 : super(key: key);
33
34 final bool active;
35 final ValueChanged<bool> onChanged;
36
37 _TapboxCState createState() => _TapboxCState();
38}
39
40class _TapboxCState extends State<TapboxC> {
41 bool _highlight = false;
42
43 void _handleTapDown(TapDownDetails details) {
44 setState(() {
45 _highlight = true;
46 });
47 }
48
49 void _handleTapUp(TapUpDetails details) {
50 setState(() {
51 _highlight = false;
52 });
53 }
54
55 void _handleTapCancel() {
56 setState(() {
57 _highlight = false;
58 });
59 }
60
61 void _handleTap() {
62 widget.onChanged(!widget.active);
63 }
64
65 Widget build(BuildContext context) {
66 // This example adds a green border on tap down.
67 // On tap up, the square changes to the opposite state.
68 return GestureDetector(
69 onTapDown: _handleTapDown, // Handle the tap events in the order that
70 onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
71 onTap: _handleTap,
72 onTapCancel: _handleTapCancel,
73 child: Container(
74 child: Center(
75 child: Text(widget.active ? 'Active' : 'Inactive',
76 style: TextStyle(fontSize: 32.0, color: Colors.white)),
77 ),
78 width: 200.0,
79 height: 200.0,
80 decoration: BoxDecoration(
81 color:
82 widget.active ? Colors.lightGreen[700] : Colors.grey[600],
83 border: _highlight
84 ? Border.all(
85 color: Colors.teal[700],
86 width: 10.0,
87 )
88 : null,
89 ),
90 ),
91 );
92 }
93}
-
Form
-
FormField
4.2 质感组件
-
Checkbox
-
DropdownButton
-
FlatButton
-
FloatingActionButton
-
IconButton
-
Radio
-
RaisedButton
-
Slider
-
Switch
-
TextField
-
Gestures , Flutter Widget 框架总览 的一节
如何创建一个按钮并使其响应用户动作链接:https://flutter.cn/docs/development/ui/widgets-intro -
Flutter 手势机制的描述 -
Flutter API documentation
所有 Flutter 库的参考文档链接:https://api.flutter-io.cn -
Flutter Gallery
一个 Demo 应用程序,展示了许多质感组件和其他 Flutter 功能链接:https://github.com/flutter/flutter/tree/master/examples/flutter_gallery -
Flutter’s Layered Design (video)
此视频包含有关有状态和无状态 widget 的信息。 由 Google 工程师 Ian Hickson 讲解。链接:https://www.youtube.com/watch?v=dkyY9WCGMi0