1.StatelessWidget与StatefulWidget
在 Flutter 中,StatelessWidget 和 StatefulWidget 是两种用于构建用户界面的基本组件类型,它们有以下区别:
StatelessWidget:StatelessWidget 是一个无状态的组件,意味着它的属性(inputs)一旦被设置,就不会发生改变。StatelessWidget 通常用于构建静态内容不会随时间变化的界面部分。例如,如果你有一个固定的文本或图标,可以使用 StatelessWidget 来表示它,因为它们不需要响应用户交互或动态更新。
StatefulWidget:StatefulWidget 是一个有状态的组件,它可以持有一些状态数据。StatefulWidget 具有一个与之关联的可变状态对象(State),可以根据状态的变化来更新界面。当状态数据发生变化时,Flutter 会调用 build() 方法重新构建界面,以反映最新的状态。StatefulWidget 适用于需要处理用户输入、展示动态数据或实现交互逻辑的场景。
2.Flutter中的四棵树
Flutter 框架的处理流程是这样的:
1.根据Widget树生成一个 Element 树,Element 树中的节点都继承自 Element 类。
2.根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自RenderObject 类。
3.根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自 Layer 类。
真正的布局和渲染逻辑在 Render 树中,Element 是 Widget 和 RenderObject 的粘合剂,可以理解为一个中间代理。
3.State生命周期
-
initState:当 widget 第一次插入到 widget 树时会被调用,对于每一个State对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后, widget 树中的InheritFrom widget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。
-
didChangeDependencies():当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget (第七章介绍),然后在之后的build() 中Inherited widget发生了变化,那么此时InheritedWidget的子 widget 的didChangeDependencies()回调都会被调用。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter 框架会通知 widget 调用此回调。需要注意,组件第一次被创建后挂载的时候(包括重创建)对应的didChangeDependencies也会被调用。
-
build():此回调读者现在应该已经相当熟悉了,它主要是用于构建 widget 子树的,会在如下场景被调用:
在调用initState()之后。
在调用didUpdateWidget()之后。
在调用setState()之后。
在调用didChangeDependencies()之后。
在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其他位置之后。
reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在 Release模式下永远不会被调用。 -
didUpdateWidget ():在 widget 重新构建时,Flutter 框架会调用widget.canUpdate来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新,如果widget.canUpdate返回true则会调用此回调。正如之前所述,widget.canUpdate会在新旧 widget 的 key 和 runtimeType 同时相等时会返回true,也就是说在在新旧 widget 的key和runtimeType同时相等时didUpdateWidget()就会被调用。
-
deactivate():当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter 框架会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
-
dispose():当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。
4.在 widget 树中获取State对象
- 通过Context获取
context对象有一个findAncestorStateOfType()方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象。
class GetStateObjectRoute extends StatefulWidget {
const GetStateObjectRoute({Key? key}) : super(key: key);
@override
State<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
}
class _GetStateObjectRouteState extends State<GetStateObjectRoute> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("子树中获取State对象"),
),
body: Center(
child: Column(
children: [
Builder(builder: (context) {
return ElevatedButton(
onPressed: () {
// 查找父级最近的Scaffold对应的ScaffoldState对象
ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;
// 打开抽屉菜单
_state.openDrawer();
},
child: Text('打开抽屉菜单1'),
);
}),
],
),
),
drawer: Drawer(),
);
}
}
上面示例中的Scaffold也提供了一个of方法
Builder(builder: (context) {
return ElevatedButton(
onPressed: () {
// 直接通过of静态方法来获取ScaffoldState
ScaffoldState _state=Scaffold.of(context);
// 打开抽屉菜单
_state.openDrawer();
},
child: Text('打开抽屉菜单2'),
);
}),
- 通过GlobalKey,两步骤
1.给目标StatefulWidget添加GlobalKey。
//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
...
Scaffold(
key: _globalKey , //设置key
...
)
2.通过GlobalKey来获取State对象
_globalKey.currentState.openDrawer()
GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了GlobalKey,那么我们便可以通过globalKey.currentWidget获得该 widget 对象、globalKey.currentElement来获得 widget 对应的element对象,如果当前 widget 是StatefulWidget,则可以通过globalKey.currentState来获得该 widget 对应的state对象。
tips:使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复。
5.状态管理
- Widget管理自身状态
- 父Widget管理子Widget的状态
- 混合状态管理
- 全局状态管理