Flutter 中文文档:为你的 Flutter 应用加入交互体验

640?wx_fmt=jpeg

你会学到什么

  • 如何响应点击。

  • 如何创建自定义 widget。

  • 无状态和有状态 widget 之间的区别。

如何修改您的应用程序以使其对用户输入做出反应?在本教程中,您将为仅包含非交互式 widget 的应用程序添加交互性。具体来说,您将通过创建一个管理两个无状态 widget 的自定义有状态 widget,修改一个图标实现使其可点击。

 中展示了如何构建下面截图所示的布局。

640?wx_fmt=jpegthe layout tutorial app

当应用第一次启动时,这个星形图标是实心红色,表明这个湖以前已经被收藏过了。 星号旁边的数字表示 41 个人已经收藏了此湖。 完成本教程后,点击星形图标将取消收藏状态,然后用轮廓线的星形图标代替实心的,并减少计数。 再次点击会重新收藏,并增加计数。
640?wx_fmt=png
为了实现这个,您将创建一个包含星形图标和计数的自定义 widget,它们都是 widget。 因为点击星形图标会更改这两个 widget 的状态,所以同一个 widget 应该同时管理这两个 widget。
您可以直接查看第二章节  创建 StatefulWidget 的子类 。 如果您想尝试不同的管理状态方式,请跳至第三章节 状态管理
1. 有状态和无状态的 widgets
有些 widgets 是有状态的, 有些是无状态的。 如果用户与 widget 交互,widget 会发生变化,那么它就是有状态的。
Stateless widget 不会发生变化。 IconIconButton  和  Text  都是无状态 widget,它们都是  StatelessWidg et  的子类。
而  stateful widget 是动态的。 例如,可以通过与用户的交互或是随着数据的改变而导致外观形态的变化。   CheckboxRadioSliderInkWellForm  和  TextField  都是有状态 widget,它们都是  StatefulWidget  的子类。
一个 widget 的状态保存在一个  State  对象中, 它和 widget 的显示分离。 Widget 的状态是一些可以更改的值, 如一个滑动条的当前值或一个复选框是否被选中。 当 widget 状态改变时, State 对象调用  setState() , 告诉框架去重绘 widget。
2. 创建一个有状态的 widget

重点是什么?

  • 实现一个有状态 widget 需要创建两个类:一个  StatefulWidget  的子类和一个  State  的子类。
  • State 类包含该 widget 的可变状态并定义该 widget 的  build()  方法.
  • 当 widget 状态改变时, State 对象调用  setState() , 告诉框架去重绘 widget。
在本节中,您将创建一个自定义有状态的 widget。 您将使用一个自定义有状态 widget 来替换两个无状态 widget —红色实心星形图标和其旁边的数字计数—该 widget 用两个子 widget 管理一行  IconButton  和  Text
实现一个自定义的有状态 widget 需要创建两个类:
  • 一个 StatefulWidget 的子类,用来定义一个 widget 类。
  • 一个  State  的子类,包含该widget状态并定义该 widget 的  build()  方法.
这一节展示如何为 Lakes 应用程序构建一个名为  FavoriteWidget  的 StatefulWidget。 第一步是选择如何管理  FavoriteWidget  的状态。
步骤1: 开始
如果你已经在  第七步: 最终的收尾   中成功创建了应用程序,你可以跳过下面的部分。

1. 确保你已经设置好了你的环境(

2.

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

如果你有一个连接并可用的设备,或者你已经启动了  (Flutter 安装部分介绍过),你就可以开始了!
步骤2: 决定哪个对象管理 widget 的状态
一个 widget 的状态可以通过多种方式进行管理,但在我们的示例中,widget 本身, FavoriteWidget ,将管理自己的状态。 在这个例子中,切换星形图标是一个独立的操作,不会影响父窗口 widget 或其他用户界面,因此该 widget 可以在内部处理它自己的状态。
你可以在  状态管理  中了解更多关于 widget 和状态的分离以及如何管理状态的信息。
步骤3: 创建 StatefulWidget 的子类
FavoriteWidget  类管理自己的状态,因此它通过重写  createState()  来创建状态对象。 框架会在构建 widget 时调用  createState() 。 在这个例子中, createState()  创建  _FavoriteWidgetState  的实例,您将在下一步中实现该实例。
lib/main.dart (FavoriteWidget)
1class FavoriteWidget extends StatefulWidget {
2  @override
3  _FavoriteWidgetState createState() => _FavoriteWidgetState();
4}

 备忘 
以下划线( _ )开头的成员或类是私有的。 有关更多信息,请参阅  Dart language tour  中的  Libraries and visibility  部分。

步骤4: 创建 State 的子类

_FavoriteWidgetState 类存储可变信息;可以在 widget 的生命周期内改变逻辑和内部状态。当应用第一次启动时,用户界面显示一个红色实心的星星形图标,表明该湖已经被收藏,并有 41 个“喜欢”。状态对象存储这些信息在 _isFavorited 和 _favoriteCount 变量中。

lib/main.dart (_FavoriteWidgetState fields)
1class _FavoriteWidgetState extends State<FavoriteWidget> {
2  bool _isFavorited = true;
3  int _favoriteCount = 41;
4  // ···
5}
状态对象也定义了  build()  方法。 这个  build()  方法创建一个包含红色  IconButton  和  Text  的行。 该 widget 使用  IconButton (而不是  Icon ),因为它具有一个  onPressed  属性,该属性定义了处理点击的回调方法( _toggleFavorite )。 你将会在接下来的步骤中尝试定义它。
lib/main.dart (_FavoriteWidgetState build)
 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}

 小提示 Placing the  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}
步骤五: 将有 stateful widget 插入 widget 树中
将您自定义 stateful widget 在  build()  方法中添加到 widget 树中。 首先,找到创建 图标文本 的代码,并删除它,在相同的位置创建 stateful widget:

640?wx_fmt=png

就是这样! 当您热重载应用后,星形图标就会响应点击了。
有问题?
如果您的代码无法运行,请在 IDE 中查找可能的错误。 调试 Flutter 应用程序  可能会有所帮助。
调试 Flutter 应用程序链接:https://flutter.cn/docs/testing/debugging
如果仍然无法找到问题,请根据 GitHub 上 的示例检查代码。
  • lib/main.dart
  • pubspec.yaml
  • lakes.jpg
本章节的其余部分介绍了可以管理 widget 状态的几种方式,并列出了其他可用的可交互的 widget。
3. 状态管理

重点是什么?

  • 有多种方法可以管理状态。
  • 您作为 widget 的设计者,需要选择使用何种管理方法。
  • 如果不是很清楚时, 就在父 widget 中管理状态。
谁管理着 stateful widget 的状态? widget 本身? 父 widget? 双方? 另一个对象? 答案是……这取决于实际情况。 有几种有效的方法可以给你的 widget 添加互动。 作为 widget 设计师,你可以基于你所期待的表现 widget 的方式来做决定。 以下是一些管理状态的最常见的方法:
  • widget 管理自己的状态
  • 父 widget 管理此 widget 的状态
  • 混搭管理
如何决定使用哪种管理方法? 以下原则可以帮助您决定:
  • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 widget 管理。
  • 如果所讨论的状态是有关界面外观效果的,例如动画,那么状态最好由 widget 本身来管理。
如果有疑问,首选是在父 widget 中管理状态。
我们将通过创建三个简单示例来举例说明管理状态的不同方式: TapboxA、TapboxB 和 TapboxC。 这些例子功能是相似的 - 每创建一个容器,当点击时,在绿色或灰色框之间切换。   _active  确定颜色: 绿色为 true,灰色为 false。
640?wx_fmt=png   640?wx_fmt=png
这些示例使用  GestureDetector  捕获 Container 上的用户动作。
3.1 widget 管理自己的状态
有时,widget 在内部管理其状态是最好的。 例如,当  ListView  的内容超过渲染框时, ListView 自动滚动。 大多数使用 ListView 的开发人员不想管理 ListView 的滚动行为,因此 ListView 本身管理其滚动偏移量。
_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}
3.2 父 widget 管理 widget 的 state
一般来说父 widget 管理状态并告诉其子 widget 何时更新通常是最合适的。 例如, IconButton  允许您将图标视为可点按的按钮。 IconButton 是一个无状态的小部件,因为我们认为父 widget 需要知道该按钮是否被点击来采取相应的处理。
在以下示例中,TapboxB 通过回调将其状态到其父类。 由于 TapboxB 不管理任何状态,因此它继承自 StatelessWidget。
ParentWidgetState 类:
  • 为 TapboxB 管理  _active  状态.
  • 实现  _handleTapboxChanged() ,当盒子被点击时调用的方法.
  • 当状态改变时,调用  setState()  更新 UI.
TapboxB 类:
  • 继承 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}

 小提示 
在创建 API 时,请考虑使用  @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}
另一种实现可能会将高亮状态导出到父 widget,同时保持 active 状态为内部,但如果您要求某人使用该 TapBox,他们可能会抱怨说没有多大意义。 开发人员只会关心该框是否处于活动状态。 开发人员可能不在乎高亮显示是如何管理的,并且倾向于让 TapBox 处理这些细节。
4. 其他交互式 widgets
Flutter 提供各种按钮和类似的交互式 widget。 这些 widget 中的大多数都实现了  Material Design guidelines ,它们定义了一组具有质感的 UI 组件。
如果你愿意,你可以使用  GestureDetector  来给任何自定义 widget 添加交互性。 您可以在  管理状态  和  Flutter Gallery  中找到 GestureDetector 的示例。
 小提示
Futter还提供了一组名为  Cupertino  的 iOS 风格的小部件。
当你需要交互性时,最容易的是使用预制的 widget。 这是预置 widget 部分列表:
4.1 标准 widgets
  • Form
  • FormField

4.2 质感组件

  • Checkbox
  • DropdownButton
  • FlatButton
  • FloatingActionButton
  • IconButton
  • Radio
  • RaisedButton
  • Slider
  • Switch
  • TextField
5. 资源
以下资源可能会在给您的应用添加交互的时候有所帮助。
  • 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

640?wx_fmt=png

猜你喜欢

转载自blog.csdn.net/weixin_43459071/article/details/102513168