Flutter开发之StatelessWidget和StatefulWidget

一、无状态StatelessWidget

StatelessWidget通常是一些没有状态(State,也可以理解成data)需要维护的Widget:

  • 它们的数据通常是直接写死(放在Widget中的数据,必须被定义为final,为什么呢?我在下一个章节讲解StatefulWidget会讲到);
  • 从parent widget中传入的而且一旦传入就不可以修改;
  • 从InheritedWidget获取来使用的数据(这个放到后面会讲解);

我们来看一下创建一个StatelessWidget的格式:

  • 1、让自己创建的Widget继承自StatelessWidget;
  • 2、StatelessWidget包含一个必须重写的方法:build方法;
class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return <返回我们的Widget要渲染的Widget,比如一个Text Widget>;
  }
}

build方法的解析

  • Flutter在拿到我们自己创建的StatelessWidget时,就会执行它的build方法;
  • 我们需要在build方法中告诉Flutter,我们的Widget希望渲染什么元素,比如一个Text Widget;
  • StatelessWidget没办法主动去执行build方法,当我们使用的数据发生改变时,build方法会被重新执行;

build方法什么情况下被执行呢?:

  • 1、当我们的StatelessWidget第一次被插入到Widget树中时(也就是第一次被创建时);
  • 2、当我们的父Widget(parent widget)发生改变时,子Widget会被重新构建;
  • 3、如果我们的Widget依赖InheritedWidget的一些数据,InheritedWidget数据发生改变时;

二、有状态StatefulWidget

2.1 介绍

如果你有阅读过默认我们创建Flutter的示例程序,那么你会发现它创建的是一个StatefulWidget。

为什么选择StatefulWidget呢?

  • 因为在示例代码中,当我们点击按钮时,界面上显示的数据会发生改变;
  • 这时,我们需要一个变量来记录当前的状态,再把这个变量显示到某个Text Widget上;
  • 并且每次变量发生改变时,我们对应的Text上显示的内容也要发生改变;

但是有一个问题,我之前说过定义到Widget中的数据都是不可变的,必须定义为final,为什么呢?

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

Flutter如何做到我们在开发中定义到Widget中的数据一定是final的呢?
我们来看一下Widget的源码:

@immutable
abstract class Widget extends DiagnosticableTree {
    // ...省略代码
}

这里有一个很关键的东西@immutable

  • 我们似乎在Dart中没有见过这种语法,这实际上是一个 注解,这涉及到Dart的元编程,我们这里不展开讲;
  • 这里我就说明一下这个@immutable是干什么的;

实际上官方有对@immutable进行说明:

  • 来源: https://api.flutter.dev/flutter/meta/immutable-constant.html
  • 说明: 被@immutable注解标明的类或者子类都必须是不可变的

2.2 如何存储Widget状态?

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

  • StatelessWidget无所谓,因为它里面的数据通常是直接定义完后就不修改的。
  • 但StatefulWidget需要有状态(可以理解成变量)的改变,这如何做到呢?

Flutter将StatefulWidget设计成了两个类:

  • 也就是你创建StatefulWidget时必须创建两个类:
  • 一个类继承自StatefulWidget,作为Widget树的一部分;
  • 一个类继承自State,用于记录StatefulWidget会变化的状态,并且根据状态的变化,构建出新的Widget;

创建一个StatefulWidget,我们通常会按照如下格式来做:

  • 当Flutter在构建Widget Tree时,会获取State的实例,并且它调用build方法去获取StatefulWidget希望构建的Widget;
  • 那么,我们就可以将需要保存的状态保存在MyState中,因为它是可变的;
class MyStatefulWidget extends StatefulWidget {
  @override
  State createState() {
    // 将创建的State返回
    return MyState();
  }
}

class MyState extends State {
  @override
  Widget build(BuildContext context) {
    return <构建自己的Widget>;
  }
}

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

2.3 案例

我们通过一个案例来练习一下StatefulWidget,还是之前的计数器案例,但是我们按照自己的方式进行一些改进。

案例效果以及布局如下:

  • Column小部件:之前我们已经用过,当有垂直方向布局时,我们就使用它;
  • Row小部件:之前也用过,当时水平方向布局时,我们就使用它;
  • RaiseButton小部件:可以创建一个按钮,并且其中有一个onPress属性是传入一个回调函数,当按钮点击时被回调;
import 'package:flutter/material.dart';

void main() {
  runApp(

    MaterialApp(
      home: Container(
        child: Center(
          child: MyCounterWidget(),
        ),
      ),
    )
  );
}

class MyCounterState extends State {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
//      child: Text("当前计数:$counter",style: TextStyle(fontSize: 30),textDirection: TextDirection.ltr,),
      child: Column (
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              RaisedButton(
                color: Colors.redAccent,
                child: Text("+1",style: TextStyle(fontSize: 18, color: Colors.white,),textDirection: TextDirection.ltr),
                onPressed: (){
                  setState(() {
                    counter++;
                  });
                },
              ),
              RaisedButton(
                color: Colors.orangeAccent,
                child: Text("-1",style: TextStyle(fontSize: 18, color: Colors.white),textDirection: TextDirection.ltr,),
                onPressed: (){
                  setState(() {
                    counter--;
                  });
                },
              ),
            ],
          ),
          Text("当前计数:$counter",style: TextStyle(fontSize: 30,color: Colors.white, decoration: TextDecoration.none)),
        ],
      ),
    );
  }
}

2.4 StatefulWidget生命周期简单版

我们知道StatefulWidget本身由两个类组成的:StatefulWidget和State,我们分开进行分析
首先,执行StatefulWidget中相关的方法:

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

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

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

main(List args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("HelloWorld"),
        ),
        body: HomeBody(),
      ),
    );
  }
}


class HomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("HomeBody build");
    return MyCounterWidget();
  }
}


class MyCounterWidget extends StatefulWidget {
  
  MyCounterWidget() {
    print("执行了MyCounterWidget的构造方法");
  }
  
  @override
  State createState() {
    print("执行了MyCounterWidget的createState方法");
    // 将创建的State返回
    return MyCounterState();
  }
}

class MyCounterState extends State {
  int counter = 0;
  
  MyCounterState() {
    print("执行MyCounterState的构造方法");
  }

  @override
  void initState() {
    super.initState();
    print("执行MyCounterState的init方法");
  }
  
  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    print("执行MyCounterState的didChangeDependencies方法");
  }

  @override
  Widget build(BuildContext context) {
    print("执行执行MyCounterState的build方法");
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              RaisedButton(
                color: Colors.redAccent,
                child: Text("+1", style: TextStyle(fontSize: 18, color: Colors.white),),
                onPressed: () {
                  setState(() {
                    counter++;
                  });
                },
              ),
              RaisedButton(
                color: Colors.orangeAccent,
                child: Text("-1", style: TextStyle(fontSize: 18, color: Colors.white),),
                onPressed: () {
                  setState(() {
                    counter--;
                  });
                },
              )
            ],
          ),
          Text("当前计数:$counter", style: TextStyle(fontSize: 30),)
        ],
      ),
    );
  }

  @override
  void didUpdateWidget(MyCounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("执行MyCounterState的didUpdateWidget方法");
  }

  @override
  void dispose() {
    super.dispose();
    print("执行MyCounterState的dispose方法");
  }
}

打印结果如下:

flutter: HomeBody build
flutter: 执行了MyCounterWidget的构造方法
flutter: 执行了MyCounterWidget的createState方法
flutter: 执行MyCounterState的构造方法
flutter: 执行MyCounterState的init方法
flutter: 执行MyCounterState的didChangeDependencies方法
flutter: 执行执行MyCounterState的build方法

// 注意:Flutter会build所有的组件两次(查了GitHub、Stack Overflow,目前没查到原因)
flutter: HomeBody build
flutter: 执行了MyCounterWidget的构造方法
flutter: 执行MyCounterState的didUpdateWidget方法
flutter: 执行执行MyCounterState的build方法

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

flutter: 执行执行MyCounterState的build方法

你可能感兴趣的:(Flutter开发之StatelessWidget和StatefulWidget)