Flutter基建 - 形影不离的State**Widget

本篇基于Flutter 3.16.4,Dart 3.2.3版本

Flutter 3.16.4 • channel stable • github.com/flutter/flu...

Framework • revision 2e9cb0aa71 (3 days ago) • 2023-12-11 14:35:13 -0700

Engine • revision 54a7145303

Tools • Dart 3.2.3 • DevTools 2.28.4

本篇为Flutter基建的第七篇文章,主要介绍Flutter中StatelessWidget和StatefulWidget使用以及基础类Widget相关知识,希望本篇文章可以帮助大家了解和熟悉Widget的基本知识,下面一起进入文章的内容吧~

Widget基类

在这之前如果你观察过StatelessWidget和StatefulWidget的源码就会发现,其实它俩都是继承自Widget抽象类。

scala 复制代码
abstract class StatelessWidget extends Widget

abstract class StatefulWidget extends Widget

二者的区别在于StatelessWidget表示的是一个无状态的Widget,它不可以更新Widget内部状态,而StatefulWidget可以通过setState来更新Widget的状态,这里暂时了解这些基本概念,先进入Widget内部看看。

dart 复制代码
@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });

  final Key? key;

  @protected
  @factory
  Element createElement();

  @override
  String toStringShort() {
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

Widget抽象类的源码很简短,它继承自DiagnosticableTree,此Tree只是用于调试所用,这里就不过多介绍,还是介绍Widget内部的几个方法。

  • createElement()方法用于创建Widget对应的Element对象,Flutter会根据具体的Element生成Render树,Render树就是渲染界面的元素;一个Widget对应一个Element,StatelessWidget创建的是StatelessElement对象,StatefulWidget创建的是StatefulElement对象,关于Widget、Element和Render三者的关系比较复杂,后面会写一篇文章详细介绍它们三个的关系和作用。
  • toStringShort()方法返回的是当前Widget的简短的信息。
  • debugFillProperties()方法用于添加一些调试属性,一般情况下很少用到。
  • canUpdate()方法作用还是比较重要的,它用于判断重建时Widget是否需要更新,默认是对比两个Widget的runtimeType和key是否相同。

Widget源码不复杂,具体复杂的是Widget和Element、Render的关系,这里先暂时了解这么多,更深入的知识我们后续慢慢理解。

StatelessWidget

基础源码

scala 复制代码
abstract class StatelessWidget extends Widget {
  const StatelessWidget({ super.key });

  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

StatelessWidget源码中实现了父类的createElement()方法,返回了一个StatelessElement对象,并且将自身传入StatelessElement构造方法当中,然后定义了一个build()方法,用于创建具体的子Widget元素。

接下来我们再看看如何使用StatelessWidget来展示界面UI:

实际使用

scala 复制代码
class MyStatelessWidget extends StatelessWidget {
  const MyStatelessWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return buildScaffold(
      context,
      const Center(
        child: Text('MyStatelessWidget'),
      ),
    );
  }
}

使用StatelessWidget时,我们只需要复写build()方法即可,然后在此方法内部将界面Widget返回,这样就可以实现一个无状态的Widget。

如果我们当前的页面需要信息的更新,那么StatelessWidget就不再适用,此时我们需要选择StatefulWidget来完成界面元素的构造,下面我们接着看看有状态的Widget是如何使用。

StatefulWidget

基础源码

scala 复制代码
abstract class StatefulWidget extends Widget {
  const StatefulWidget({ super.key });

  @override
  StatefulElement createElement() => StatefulElement(this);

  @protected
  @factory
  State createState();
}

StatefulWidget源码中也是实现了父类的createElement()方法,返回了一个StatefulElement对象,并且将自身传入StatefulElement构造方法当中,和StatelessWidget不同的是,它并没有定义build()方法用于创建界面Widget,而是定义了一个createState()方法,用于创建一个State对象,此对象就是用于保存当前Widget中相关的状态信息,更新状态信息也是通过State来完成。

了解了StatefulWidget基础源码之后,我们紧接着来看看如何使用StatefulWidget和State展示一个可修改状态信息的Widget。

实际使用

scala 复制代码
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});

  @override
  State<StatefulWidget> createState() {
    return _MyState();
  }
}

class _MyState extends State<MyStatefulWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter组件'),
      ),
      body: Center(
        child: Text('count: $count'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            ++count;
          });
        },
        child: const Icon(Icons.favorite),
      ),
    );
  }
}

通过上述的代码可以看到StatefulWidget中逻辑变得很少了,只是复写了createState()方法,返回了_MyState对象而已,具体的子Widget如何创建,Widget的状态信息都下放到_MyState当中了,_MyState继承自State类,然后复写了build()方法,创建界面Widget,并且在内部还定义了一个count变量,用于中间Text显示的文本数据,然后在FloatingActionButton的点击事件中通过setState()方法更新count值,随之Text文本也会跟随着count变化而及时更新。

StatefulWidget可以依赖State对象对内部的状态信息做出更新操作,这也是它和StatelessWidget最大的不同,如果你开发的界面需要界面更新,那么选择StatefulWidget来编写界面是当之无愧的。

下面我们再来看看如何通过State感知当前Widget的生命周期变化。

生命周期

scss 复制代码
class _MyState extends State<MyStatefulWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    debugPrint('build');
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter组件'),
      ),
      body: Center(
        child: Text('count: $count'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            ++count;
          });
        },
        child: const Icon(Icons.favorite),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    debugPrint('initState');
  }

  @override
  void didUpdateWidget(covariant MyStatefulWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    debugPrint('didUpdateWidget');
  }

  @override
  void deactivate() {
    super.deactivate();
    debugPrint('deactivate');
  }

  @override
  void activate() {
    super.activate();
    debugPrint('activate');
  }

  @override
  void reassemble() {
    super.reassemble();
    debugPrint('reassemble');
  }

  @override
  void dispose() {
    super.dispose();
    debugPrint('dispose');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint('didChangeDependencies');
  }
}

上述代码只是简单的在各个生命周期中加了debug信息的打印,下面我们逐一来看下每个生命周期是如何调用的。

当界面第一次被打开时:

initState
didChangeDependencies
build

我们会发现当界面第一次被打开时,生命周期的变化是initStaste -> didChangeDependencies -> build

当界面退出时:

deactivate
dispose

界面从打开状态变为退出状态时,生命周期的变化是deactivate -> dispose

当我们点击Android Studio的hot reload时:

reassemble
build

当我们点击FloatingActionButton将count值改变时:

build

此时之后调用build()方法进行界面的重绘。

分析到这为止,我们可以大致将State的生命周期给梳理成一个框架:

上面这个流程图就将State的从进入界面到退出界面,中间包括hot reload动作和initState动作的生命周期就完整的表述出来了,如果大家有疑问不理解的地方,欢迎在评论区沟通交流😊

不知道看到这里之后,大家有没有发现,activate和didUpdateWidget这两个方法好像自始至终都没有被调用过,难道它们二者不会在生命周期的过程中呈现出来么?不会的,只是它们两个比较特殊,有对应的触发条件,下面我们一个一个来解释下。

didUpdateWidget表示每当Widget配置改变时都会被调用,那么大家是否有种疑问,上面调用setState方法时count不是已经改变了么,为啥没有被调用呢,didUpdateWidget调用时机是父Widget的配置改变需要重建时,子Widget会调用此didUpdateWidget方法进行重建,接下来我们通过代码验证下。

我们将_MyState中原先Center中Text去掉,换成MyFulWidgetChild()组件,此组件中复写didUpdateWidget方法。

scala 复制代码
class _ChildState extends State<MyFulWidgetChild> {
  @override
  Widget build(BuildContext context) {
    return const Text('Child');
  }

  @override
  void didUpdateWidget(covariant MyFulWidgetChild oldWidget) {
    super.didUpdateWidget(oldWidget);
    debugPrint('child didUpdateWidget');
  }
}

此时我们点击_State中FloatingActionButton改变count值时,_ChildState的didUpdateWidget方法就会被调用了,看到这大家应该就会明白了此生命周期的触发的具体条件了。

还剩最后一个activate生命周期,此生命周期根据注释可以得出它的触发条件是:当Widget通过deactivate被移出界面时同时又被加入到另外一个界面它才会被调用,此生命周期日常开发中基本不会使用。

写在最后

本篇文章简单概括性的介绍了Widget和StatelessWidget、StatefulWidget的相关知识,希望可以帮助大家了进一步了解和熟悉StatelessWidget和StatefulWidget的相关知识,后续会循序渐进逐步接触Flutter更多的知识。

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~

相关推荐
Winston Wood26 分钟前
Android Parcelable和Serializable的区别与联系
android·序列化
清风徐来辽30 分钟前
Android 项目模型配置管理
android
帅得不敢出门1 小时前
Gradle命令编译Android Studio工程项目并签名
android·ide·android studio·gradlew
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai2 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书