一. StatefulWidget

1.1. 认识StatefulWidget

1.1.1. StatefulWidget介绍

定义到Widget中的数据都是不可变的,必须定义为final,为什么?

  • Flutter在设计的时候就决定了一旦Widget中展示的数据发生变化,就重新构建整个Widget;
  • Flutter通过一些机制来限定定义到Widget中的成员变量必须是final的;

Flutter如何做到我们在开发中定义到Widget中的数据一定是final的呢?

  1. @immutable
  2. abstract class Widget extends DiagnosticableTree {
  3. // ...省略代码
  4. }
  • 这实际上是一个 注解;
  • 被@immutable注解标明的类或者子类都必须是不可变的

    1.1.2. 如何存储Widget状态?

    既然Widget是不可变,那么StatefulWidget如何来存储可变的状态呢?

  • StatelessWidget无所谓,其数据通常是直接定义完后就不修改的;

  • StatefulWidget需要有状态(可以理解成变量)的改变;

Flutter将StatefulWidget设计成了两个类:

  • 一个类继承自StatefulWidget,作为Widget树的一部分;
  • 一个类继承自State,用于记录StatefulWidget会变化的状态,并且根据状态的变化,构建出新的Widget;

创建一个StatefulWidget:

  • Flutter在构建Widget Tree时,会获取State的实例,并且它调用build方法去获取StatefulWidget希望构建的Widget;
  • 可以将需要保存的状态保存在MyState中,因为它是可变的;

    1. class MyStatefulWidget extends StatefulWidget {
    2. @override
    3. State<StatefulWidget> createState() {
    4. // 将创建的State返回
    5. return MyState();
    6. }
    7. }
    8. class MyState extends State<MyStatefulWidget> {
    9. @override
    10. Widget build(BuildContext context) {
    11. return <构建自己的Widget>;
    12. }
    13. }

    思考:为什么Flutter要这样设计呢?
    Flutter中,只要数据改变了Widget就需要重新构建(rebuild)

    1.2. StatefulWidget案例

    1.2.1. 案例效果和分析

    计数器案例:

  • Column小部件:垂直方向布局;

  • Row小部件:水平方向布局;
  • RaiseButton小部件:按钮,onPress属性是传入一个回调函数,当按钮点击时被回调;

    1.2.2. 创建StatefulWidget

  • 当点击按钮时,数字会发生变化,所以我们需要使用一个StatefulWidget,所以需要创建两个类;

  • MyCounterWidget继承自StatefulWidget,里面需要实现createState方法;
  • MyCounterState继承自State,里面实现build方法,并且可以定义一些成员变量;

    1. class MyCounterWidget extends StatefulWidget {
    2. @override
    3. State<StatefulWidget> createState() {
    4. // 将创建的State返回
    5. return MyCounterState();
    6. }
    7. }
    8. class MyCounterState extends State<MyCounterWidget> {
    9. int counter = 0;
    10. @override
    11. Widget build(BuildContext context) {
    12. return Center(
    13. child: Text("当前计数:$counter", style: TextStyle(fontSize: 30),),
    14. );
    15. }
    16. }

    1.2.3. 实现按钮的布局

    1. class MyCounterState extends State<MyCounterWidget> {
    2. int counter = 0;
    3. @override
    4. Widget build(BuildContext context) {
    5. return Center(
    6. child: Column(
    7. mainAxisAlignment: MainAxisAlignment.center,
    8. children: <Widget>[
    9. Row(
    10. mainAxisAlignment: MainAxisAlignment.center,
    11. children: <Widget>[
    12. RaisedButton(
    13. color: Colors.redAccent,
    14. child: Text("+1", style: TextStyle(fontSize: 18, color: Colors.white),),
    15. onPressed: () {
    16. },
    17. ),
    18. RaisedButton(
    19. color: Colors.orangeAccent,
    20. child: Text("-1", style: TextStyle(fontSize: 18, color: Colors.white),),
    21. onPressed: () {
    22. },
    23. )
    24. ],
    25. ),
    26. Text("当前计数:$counter", style: TextStyle(fontSize: 30),)
    27. ],
    28. ),
    29. );
    30. }
    31. }

    1.2.4. 按钮点击状态改变

    监听状态的改变,当状态改变时要修改counter变量:

  • 直接修改变量不可以改变界面。

  • Flutter并不知道数据发生了改变,需要来重新构建我们界面中的Widget;

如何让Flutter知道状态发生改变重新构建Widget呢?

  • 需要调用一个State中默认提供的setState方法;
  • 可以在其中的回调函数中修改我们的变量;

    1. onPressed: () {
    2. setState(() {
    3. counter++;
    4. });
    5. },

    1.3. StatefulWidget生命周期

    1.3.1. 生命周期的理解

  • 客户端开发:iOS开发中我们需要知道UIViewController从创建到销毁的整个过程,Android开发中我们需要知道Activity从创建到销毁的整个过程。以便在不同的生命周期方法中完成不同的操作;

Flutter小部件的生命周期:

  • StatelessWidget可以由父Widget直接传入值,调用build方法来构建,整个过程非常简单;
  • StatefulWidget需要通过State来管理其数据,并且还要监控状态的改变决定是否重新build整个Widget;
  • 主要讨论StatefulWidget的生命周期,也就是它从创建到销毁的整个过程;

    1.3.2. 生命周期的简单版

    StatefulWidget有哪些生命周期的回调呢?分别在什么情况下执行?

  • 灰色部分的内容是Flutter内部操作的,并不需要手动去设置它们;

  • 白色部分表示可以去监听到或者可以手动调用的方法;

StatefulWidget本身由两个类组成的:StatefulWidget和State
Flutter-StatefulWidget - 图1
执行StatefulWidget中相关的方法:

  • 1、执行StatefulWidget的构造函数(Constructor)来创建出StatefulWidget;
  • 2、执行StatefulWidget的createState方法,来创建一个维护StatefulWidget的State对象;

State object,调用createState创建State对象时,执行State类的相关方法:

  • 1、执行State类的构造方法(Constructor)来创建State对象;
  • mounted:Flutter内部操作
  • 2、执行initState,我们通常会在这个方法中执行一些数据初始化的操作,或者也可能会发送网络请求;
    • 注意:这个方法是重写父类的方法,必须调用super,因为父类中会进行一些其他操作;
    • 注解(annotation):@mustCallSuper
  • 3、执行didChangeDependencies方法,这个方法在两种情况下会调用
    • 情况一:调用initState会调用;
    • 情况二:从其他对象中依赖一些数据发生改变时,比如InheritedWidget;
  • 4、Flutter执行build方法,来看一下我们当前的Widget需要渲染哪些Widget;
  • 5、当前的Widget不再使用时,会调用dispose进行销毁;
  • 6、手动调用setState方法,会根据最新的状态(数据)来重新调用build方法,构建对应的Widgets;
  • 7、didUpdateWidget是在当父Widget触发重建(rebuild)时,系统会调用didUpdateWidget方法;

代码演示:

  1. import 'package:flutter/material.dart';
  2. main(List<String> args) {
  3. runApp(MyApp());
  4. }
  5. class MyApp extends StatelessWidget {
  6. @override
  7. Widget build(BuildContext context) {
  8. return MaterialApp(
  9. home: Scaffold(
  10. appBar: AppBar(
  11. title: Text("HelloWorld"),
  12. ),
  13. body: HomeBody(),
  14. ),
  15. );
  16. }
  17. }
  18. class HomeBody extends StatelessWidget {
  19. @override
  20. Widget build(BuildContext context) {
  21. print("HomeBody build");
  22. return MyCounterWidget();
  23. }
  24. }
  25. class MyCounterWidget extends StatefulWidget {
  26. MyCounterWidget() {
  27. print("执行了MyCounterWidget的构造方法");
  28. }
  29. @override
  30. State<StatefulWidget> createState() {
  31. print("执行了MyCounterWidget的createState方法");
  32. // 将创建的State返回
  33. return MyCounterState();
  34. }
  35. }
  36. class MyCounterState extends State<MyCounterWidget> {
  37. int counter = 0;
  38. MyCounterState() {
  39. print("执行MyCounterState的构造方法");
  40. }
  41. @override
  42. void initState() {
  43. super.initState();
  44. print("执行MyCounterState的init方法");
  45. }
  46. @override void didChangeDependencies() {
  47. // TODO: implement didChangeDependencies
  48. super.didChangeDependencies();
  49. print("执行MyCounterState的didChangeDependencies方法");
  50. }
  51. @override Widget build(BuildContext context) {
  52. print("执行执行MyCounterState的build方法");
  53. return Center(
  54. child: Column(
  55. mainAxisAlignment: MainAxisAlignment.center,
  56. children: <Widget>[
  57. Row(
  58. mainAxisAlignment: MainAxisAlignment.center,
  59. children: <Widget>[
  60. RaisedButton(
  61. color: Colors.redAccent,
  62. child: Text("+1", style: TextStyle(fontSize: 18, color: Colors.white),),
  63. onPressed: () {
  64. setState(() {
  65. counter++;
  66. });
  67. },
  68. ),
  69. RaisedButton(
  70. color: Colors.orangeAccent,
  71. child: Text("-1", style: TextStyle(fontSize: 18, color: Colors.white),),
  72. onPressed: () {
  73. setState(() {
  74. counter--;
  75. });
  76. },
  77. )
  78. ],
  79. ),
  80. Text("当前计数:$counter", style: TextStyle(fontSize: 30),)
  81. ],
  82. ),
  83. );
  84. }
  85. @override
  86. void didUpdateWidget(MyCounterWidget oldWidget) {
  87. super.didUpdateWidget(oldWidget);
  88. print("执行MyCounterState的didUpdateWidget方法");
  89. }
  90. @override
  91. void dispose() {
  92. super.dispose();
  93. print("执行MyCounterState的dispose方法");
  94. }
  95. }

打印结果如下:

  1. flutter: HomeBody build
  2. flutter: 执行了MyCounterWidget的构造方法
  3. flutter: 执行了MyCounterWidget的createState方法
  4. flutter: 执行MyCounterState的构造方法
  5. flutter: 执行MyCounterState的init方法
  6. flutter: 执行MyCounterState的didChangeDependencies方法
  7. flutter: 执行执行MyCounterState的build方法
  8. // 注意:Flutter会build所有的组件两次
  9. flutter: HomeBody build
  10. flutter: 执行了MyCounterWidget的构造方法
  11. flutter: 执行MyCounterState的didUpdateWidget方法
  12. flutter: 执行执行MyCounterState的build方法

改变状态,手动执行setState方法后会打印如下结果:

  1. flutter: 执行
  2. 执行MyCounterState的build方法

1.3.3. 生命周期的复杂版

1、mounted是State内部设置的一个属性;

  • 内部设置的,不需要手动进行修改;

2、dirty state的含义是脏的State

  • 它实际是通过一个Element的东西的属性来标记的;
  • 将它标记为dirty会等待下一次的重绘检查,强制调用build方法来构建我们的Widget;

3、clean state的含义是干净的State

  • 表示当前build出来的Widget,下一次重绘检查时不需要重新build;

    二. Flutter的编程范式

    2.1. 编程范式的理解

    面向对象编程、面向过程编程、函数式编程、面向协议编程;
    另外还有两个对应的编程范式:命令式编程 和 声明式编程

  • 命令式编程: 命令式编程非常好理解,就是一步步给计算机命令,告诉它想做什么事情;

  • 声明式编程: 声明式编程通常是描述目标的性质,你应该是什么样的,依赖哪些状态,并且当依赖的状态发生改变时,我们通过某些方式通知目标作出响应;

    2.3. Flutter的编程范式

    目前在Vue、React、包括iOS中的SwiftUI中以及Flutter目前都采用了声明式编程。
    声明式编程,通常会维护一套数据集:

  • 这个数据集可能来自己父类、来自自身State管理、来自InheritedWidget、来自统一的状态管理的地方;

  • 有这么一个数据集,并且告诉Flutter这些数据集在哪里使用;
  • 然后声明期望最后的状态是什么样的,Flutter内部帮助我们改变这个状态;

statelessWidget 转statulWidget alt + enter
将children抽取为一个独立的widget alt + enter + w