环境介绍以及参考文献
本示例是在 Linux 16.04.1-Ubuntu 搭配 VS Code 使用。
Widget 与 Element
- Widget 实际上就是 Element 的配置数据,Widget 树实际上是一个配置树,而真正的 UI 渲染树是由 Element 构成;不过,由于 Element 是通过 Widget 生成的,所以它们之间有对应关系,在大多数场景,我们可以宽泛地认为 Widget 树就是指 UI 控件树或 UI 渲染树。
- 一个 Widget 对象可以对应多个 Element 对象。这很好理解,根据同一份配置(Widget),可以创建多个实例(Element)。
Widget 类
@immutable
abstract class Widget extends DiagnosticableTree { //DiagnosticableTree 类提供调试信息
const Widget({ this.key });
final Key key; //决定是否在下一次 build 时复用旧的 widget,决定的条件在 canUpdate() 方法中
@protected
Element createElement(); //Flutter Framework 在构建 UI 树时,会先隐式调用此方法生成对应节点的 Element 对象
@override
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key'; //toString 方法
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { //复写父类 DiagnosticableTree 的方法,设置诊断树的一些特性
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
static bool canUpdate(Widget oldWidget, Widget newWidget) { // 是否用新的 Widget 对象去更新就 UI 树上对应的 Element 对象的配置。
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
Widget 类本身是一个抽象类,其中最核心的就是定义了 createElement() 接口,在 Flutter 开发中,我们一般都不用直接继承 Widget 类来实现一个新组件,相反,我们通常会通过继承 StatelessWidget 或 StatefulWidget 来间接继承 Widget 类来实现。
StatelessWidget
StatelessWidget 比较简单,继承 Widget 类,重写了 createElement() 方法。
// StatelessWidget 类的定义,是一个抽象类
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key? key }) : super(key: key); //构造函数
@override
StatelessElement createElement() => StatelessElement(this); //StatelessElement 类简介继承自 Element 类
@protected
Widget build(BuildContext context); //子类将在 build 方法中通过嵌套其他 Widget 来构建 UI。
}
// 一个示例:
class Echo extends StatelessWidget {
const Echo({
Key? key,
required this.text,
this.backgroundColor:Colors.grey,
}):super(key:key);
// Widget 的属性应尽可能的被声明为 final,防止被意外改变。
final String text;
final Color backgroundColor;
@override
Widget build(BuildContext context) {
return Center(
child: Container(
color: backgroundColor,
child: Text(text),
),
);
}
}
// 使用示例:
Widget build(BuildContext context) {
return Echo(text: "hello world");
}
StatefulWidget
StatefulWidget 也是继承自 Widget 类,并重写了 createElement() 方法,不同的是返回的 Element 对象并不相同;
另外 StatefulWidget 类中添加了一个新的接口 createState()。
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key? key }) : super(key: key);
// StatefulElement 间接继承自 Element 类
// StatefulElement 中可能会多次调用 createState() 来创建状态(State)对象
@override
StatefulElement createElement() => StatefulElement(this);
// createState() 用于创建和 Stateful widget 相关的状态,它在 Stateful widget 的生命周期中可能会被多次调用。
// 例如,当一个 Stateful widget 同时插入到 widget 树的多个位置时,Flutter framework 就会调用该方法为每一个位置生成一个独立的 State 实例,
// 其实,本质上就是一个 StatefulElement 对应一个 State 实例。
@protected
@factory
State createState();
}
State
一个 StatefulWidget 类会对应一个 State 类,State 表示与其对应的 StatefulWidget 要维护的状态,State 中的保存的状态信息有两个作用:
- 在 widget 构建时可以被同步读取。
- 在 widget 生命周期中可以被改变,当 State 被改变时,可以手动调用其 setState() 方法通知 Flutter framework 状态发生改变,Flutter framework 在收到消息后,会重新调用其 build 方法重新构建 widget 树,从而达到更新 UI 的目的。
State 中有两个常用属性:
- widget,它表示与该 State 实例关联的 widget 实例,由 Flutter framework 动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI 树上的某一个节点的 widget 实例在重新构建时可能会变化,但 State 实例只会在第一次插入到树中时被创建,当在重新构建时,如果 widget 被修改了,Flutter framework 会动态设置 State.widget 为新的 widget 实例。
- context,StatefulWidget 对应的 BuildContext,作用同 StatelessWidget 的 BuildContext。
理解 State 生命周期
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
// 当 Widget 第一次插入到 Widget 树时被调用,对于每一个 State 对象,只会被调用一次。
@override
void initState() {
super.initState();
print("initState");
}
// 在 Widget 重新构造时,当新旧 widget 的 key 和 runtimeType 同事相等时被调用
@override
void didUpdateWidget(MyHomePage oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
// 当 State 对象从树中移除时被调用
@override
void deactivate() {
super.deactivate();
print("deactive");
}
// 当 State 对象从树中被永久移除时被调用
@override
void dispose() {
super.dispose();
print("dispose");
}
// 在热重载时被调用,在 Release 模式下永不会被调用
@override
void reassemble() {
super.reassemble();
print("reassemble");
}
// 当 State 对象的依赖发生变化时被调用,典型的场景是当系统语言 Local 或应用主题改变时,被回调。
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
// 在构建 Widget 子树时被调用
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
在 Widget 树中获取 State 对象
需要注意的地方,showSnackBar 的句柄改变了,需要用 ScaffoldMessengerState 去操作,具体请看下面的代码
通过 Context 获取
context 对象有一个 findAncestorStateOfType() 方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象。
例:(打开 SnackBar)
Scaffold(
appBar: AppBar(
title: Text("子树中获取State对象"),
),
body: Center(
child: Builder(builder: (context) {
return RaisedButton(
onPressed: () {
// 查找父级最近的Scaffold对应的ScaffoldState对象
ScaffoldMessengerState? _state = context.findAncestorStateOfType<ScaffoldMessengerState>();
//调用ScaffoldState的showSnackBar来弹出SnackBar
_state?.showSnackBar(
SnackBar(
content: Text("我是SnackBar"),
),
);
},
child: Text("显示SnackBar"),
);
}),
),
);
// Flutter 开发中便有了一个默认的约定:
// 如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个 of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;
// 如果 State 不希望暴露,则不提供 of 方法。
// eg.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("我是SnackBar"),
),
);
通过 GlobalKey 获取
GlobalKey 是 Flutter 提供的一种在整个 APP 中引用 element 的机制。
如果一个 widget 设置了 GlobalKey,那么我们便可以通过 globalKey.currentWidget 获得该 widget 对象、globalKey.currentElement 来获得 widget 对应的element 对象,如果当前 widget 是 StatefulWidget,则可以通过 globalKey.currentState 来获得该 widget 对应的 state 对象。
GlobalKey 开销较大,如果有其他方案,尽量避免使用。
//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
Scaffold(
key: _globalKey , //设置key
)
_globalKey.currentState.openDrawer()