1、概念
Widget 是 UI 控件的基本抽象,它负责描述 UI 的一部分应该如何构建。每个Widget 都有一个对应的 RenderObject,负责实际的布局和绘制工作。Widget 不直接参与布局或绘制过程;它们只是描述了如何构建用户界面的一部分,并且可以被组合起来创建更复杂的 UI。
2、Widget接口
Wiget类本身是一个抽象类,最核心的部分是定义了
createElement()
接口,在Flutter实际开发中,我们使用StatelessWidget和StatefulWidget间接继承Widget来实现新组件,比如创建Flutter项目时生成的示例代码。
abstract class Widget extends DiagnosticableTree {const Widget({required this.key});final Key key; Element createElement(); String toStringShort() {return '$runtimeType';}void debugFillproperties(DiagnosticPropertiesBuilder properties) {super.debugFillProperties(properties);properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;}static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType &&oldWidget.key == newWidget.key;}
}
-
DiagnosticableTree:诊断树,它在Flutter框架中主要用于生成诊断信息,帮助我们更好的理解程序的运行状态;
-
key:决定是否在下一次创建时复用旧的Widget,条件在
canUpdate()
中; -
createElement()
:Flutter Framework构建UI树时,优先调用此方法生成对应节点的Element对象,Flutter Framework在调用时为隐式调用; -
DiagnosticableTree 方法:
方法 作用 toString()
返回对象的字符串表示形式。 toStringShort()
返回对象的简短字符串表示形式。 toStringDeep()
返回对象及其子树的详细字符串表示形式。 toStringShallow()
返回对象本身的详细字符串表示形式。 toStringProperties()
返回对象的属性信息。 debugFillProperties()
填充诊断属性构建器,用于生成详细的诊断信息。 -
canUpdate()
是否使用旧的Widget对象更新旧UI树上对应Element配置,如果两个Widget的runtimeType和Key同时相等,则认为它们可以更新。
3、StatelessWidget
StatelessWidget继承自Widget类重写了
createElement()
方法:class Echo extends StatelessWidget { const Echo({super.key}); StatelessElement createElement() => StatelessElement(this); Widget build(BuildContext context) {// TODO: implement buildthrow UnimplementedError(); } }
- 显示文本
class Echo extends StatelessWidget {final String text;final Color backgroundColor;const Echo({super.key,required this.text,this.backgroundColor = Colors.grey,required String title}); StatelessElement createElement() => StatelessElement(this); Widget build(BuildContext context) {return Center(child: Container(color: backgroundColor,child: Text(text),),);}
}
- 调用
class _MyHomePageState extends State<MyHomePage> { Widget build(BuildContext context) {return const Echo(text: "示例文本", title: "StatelessWidget");}
}
4、StatefulWidget
StatefulWidget和StatelessWidget一样也继承自Widget类 ,StatefulWidget也重写了
createElement()
方法,返回的Element对象不同;而且StatefulWidget类中添加了一个新的接口createState()
。
- StatefulWidget源码
abstract class StatefulWidget extends Widget {const StatefulWidget({ super.key }); StatefulElement createElement() => StatefulElement(this);State createState();
}
- StatefulElement:间接调用Element类,作为StatefulWidget的配置数据相对应,StatefulElement是Element的子类,它可以多次调用
createState()
来创建状态对象; createState()
:抽象方法,必须在具体的StatefulWidget子类中实现,用于创建Stateful Widget相关的状态,State对象负责维持Widget的状态,并提供一种机制触发Widget的重新构建。
5、State
State类是与StatefulWidget一起工作的核心部分之一,当创建一个createState方法,该方法返回一个State类的实例,这个State类负责管理Widget的状态,并提供了更新UI的方法。
State中保存的状态信息可以进行如下操作:
- Widget构建时可以被同步读取;
- Widget生命周期可以被改变,当State发生改变时,可以手动调用build重构Widget树,以达到更新UI的目的。
5.1 State生命周期
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key}); Widget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),useMaterial3: true,),home: const MyHomePage(),);}
}class MyHomePage extends StatelessWidget {const MyHomePage({super.key}); Widget build(BuildContext context) {// return const CounterWidget();return const Text("测试文本");}
}class CounterWidget extends StatefulWidget {final int initValue;const CounterWidget({super.key, required this.initValue}); State<CounterWidget> createState() => _CounterWidgetStates();
}class _CounterWidgetStates extends State<CounterWidget> {late int _counter;bool _hotReloadTrigger = false;void initState() {super.initState();_counter = widget.initValue;if (kDebugMode) {print("initState");}} Widget build(BuildContext context) {if (kDebugMode) {print("build");}// 使用 _hotReloadTrigger 来触发构建变化return Scaffold(appBar: AppBar(title: Text(_hotReloadTrigger ? 'Hot Reload Triggered' : 'Counter'),),body: Center(child: ElevatedButton(onPressed: () => setState(() => ++_counter),child: Text("$_counter"),),),);}void didUpdateWidget(covariant CounterWidget oldWidget) {super.didUpdateWidget(oldWidget);if (kDebugMode) {print("didUpdateWidget");}}void reassemble() {super.reassemble();if (kDebugMode) {print("reassemble");}// 改变属性以触发 didUpdateWidgetsetState(() {_hotReloadTrigger = !_hotReloadTrigger;// 创建新的 CounterWidget 实例以触发 didUpdateWidgetfinal newWidget = CounterWidget(initValue: widget.initValue);if (newWidget != widget) {if (kDebugMode) {print("$newWidget");}}});}void didChangeDependencies() {super.didChangeDependencies();if (kDebugMode) {print("didChangeDependencies");}}
}
-
修改MyHomePage类,添加跳转按钮,控制台的输出日志如下:
I/flutter ( 5101): initState I/flutter ( 5101): didChangeDependencies I/flutter ( 5101): build
-
点击热重载,控制台的输出日志如下:
I/flutter ( 5101): reassemble I/flutter ( 5101): build
-
修改build,返回文本:
class MyHomePage extends StatelessWidget {const MyHomePage({super.key}); Widget build(BuildContext context) {// return const CounterWidget();return const Text("测试文本");} }
-
输出日志为
I/flutter ( 5101): reassemble I/flutter ( 5101): deactivate I/flutter ( 5101): dispose
-
5.2 回调函数说明
-
build()
:负责构建组件的实际UI,它在以下几种情况下调用:- 在调用
initState()
、didUpdateWidget()
、setState()
、didChangeDependencies()
这几种方法之后; - 在State对象从树中一个位置移除又插入到树的其他位置之后。
- 在调用
-
initState()
:当Widget第一次插入到树中调用,执行初始化工作的最佳时机,此方法只会调用一次,之后即使组件重建也不会再次调用; -
didChangeDependencies()
:当State对象的依赖发生变化时会被调用,例如当应用的主题或者语言发生变化,Flutter framework会通知Widget调用此回调; -
reassemble()
:为开发调试使用,点击热重载后会调用此方法; -
didUpdateWidget()
:检测Widget树某一节点是否需要更新,在新旧Widget的Key和runtimeType同时相等时就调用didUpdateWidget()
; -
deactivate()
:当State对象暂时从树中移除但未来可能重新插入的时候调用; -
dispose()
:当State对象永久从树中移除时调用,只调用一次,调用后该State对象不能再用于构建UI。