本篇基于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,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~