Flutter UI 绘制与 InheritedWidget 解析
Flutter UI 绘制与 InheritedWidget 解析
Flutter 的Widget
分为StatefulWidget
和StatelessWidget
,二者都继承自Widget
。
此外还有一种用来传输数据的Widget
——InheritedWidget
,与上述两者不太一样的是,他的继承关系是:InheritedWidget
→ProxyWidget
→Widget
。
Flutter 的渲染流程如图:
可以简单理解为, Widget
是配置信息,Element
代表在树中详细的位置,而RenderObject
则是实际渲染的对象。
StatelessWidget
和StatefulWidget
在创建之后就不会再变化,而StatefulWidget
因为有State
,所以可以在State
调用setState()
方法之后,重新执行State
的build()
方法,从而更新界面。
如果Widget
是const
的,那么他就不会被rebuild
。
Widget Rebuild 的过程
以 StatefulWidget 为例:
调用
setState()
方法,会调用对应的Element
的markNeedsBuild()
方法,通过BuildOwner
的scheduleBuildFor(Element element)
方法将当前Element
标记为dirty
,以便在下次屏幕刷新时安排rebuilt
。下一帧屏幕刷新,调用
BuildOwner
的buildScope(Element context, [ VoidCallback? callback ])
方法。这个方法会遍历_dirtyElements
中所有dirty
的element
执行element.rebuild();
方法,在其内部调用了Element
的performRebuild()
方法。Element
的performRebuild()
方法因各个Element
的实现而异:StatelessElement
、InheritedElement
:与父类ComponentElement
保持一致StatefulElement
:判断有需要时调用state.didChangeDependencies();
,其余与父类ComponentElement
保持一致
而ComponentElement
的performRebuild()
主要做了 2 件事:
(1)built = build();
;(2)_child = updateChild(_child, built, slot);
在这其中build()
:
StatelessElement
:build() => widget.build(this);
StatefulElement
:build() => state.build(this);
InheritedElement
:build() => widget.child;
updateChild
会判断以下几种情况:
newWidget == null | newWidget != null | |
---|---|---|
child == null | return null | return new Element |
child != null | remove old child, return null | Old child updated if possible, returns child or new Element |
其中,old child updated
的时候调用的是child.update(newWidget);
方法会触发Widget
的rebuild()
。
这样就完成了一次 Rebuild。
InheritedWidget 的 Rebuild 过程
InheritedWidget
是持有状态的Widget
,他的子Widget
可以通过他来获取这些状态。
一般来说,InheritedWidget
持有的状态是final
的,如果要更新状态,就需要在其外部包裹一个StatefulWidget
,通过StatefulWidget
的State.setState()
来触发InheritedWidget
重建(实际上InheritedElement
没有重新创建),从而更新那些依赖了InheritedWidget
的子Widget
。
下图是一个被StatefulWidget
包裹的InheritedWidget
在setSate(){}
方法执行后的流程图:
当外层StatefulWidget
的Element
执行到updateChild(child,build,solt);
会调用InheritedElement
的update()
方法。
这个方法内部会调用updated(oldWidget)
方法,在内部通过notifyClients(oldWidget);
方法,通知原先的InheritedElement
的_dependents
,将其标记为dirty
,准备rebuild
。
在此之后,update()
方法还会将当前Element
标记为dirty
,通过调用rebuild();
执行performRebuild();
在performRebuild()
方法中:
built = build();
中的build
方法:build() => widget.child;
实际上取了widget
的child
。- 然后执行
_child = updateChild(_child, built, slot);
这个过程与普通Widget
一致。
需要注意的是,updateChild
中,如果子Widget
不是const
(或者被InheritedWidget
外层的widget
/state
之类的持有)就会被认为built!=_child
从而导致InheritedWidget
的子Widget
重建。导致的结果就是:虽然InheritedWidget
的确只标记了那些依赖了他的Widget
,但是由于直接子Widget
要重建,所以还是所有的非const Widget
都重建了。
InheritedWidget 的获取方式
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
获取指定类型的InheritedWidget
,并且将自己注册到此Widget
,以便当该Widget
变化的时候,自己也能rebuilt
。复杂度O(1)
。T? findAncestorWidgetOfExactType<T extends Widget>();
只获取指定类型的Widget
,包括InheritedWidget
,仅获取该Widget
执行一些操作,通常用在interaction event handlers
之类中。复杂度(O(N)
。
代码示例
根据上述理论,创建一个InheritedWidget
来传递数据:
1、AppColor.dart
一个持有color
的InheritedWidget
。
class AppColor extends InheritedWidget {
final Color color;
final Widget child;
Function(Color)? onColorChanged;
AppColor({
required this.color,
required this.child,
this.onColorChanged,
}) : super(child: child);
@override
bool updateShouldNotify(covariant AppColor oldWidget) =>
color != oldWidget.color;
static AppColor? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppColor>();
}
}
2、定义一些类,使用或未使用到InheritedWidget
:
class NoName extends StatelessWidget {
const NoName({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
print("NoName build ${this.hashCode}");
return Column(
children: [
Column(
children: [
// 这里 AppColor 的_dependents 会加入 ColorfulContainer(dependencies: [AppColor])
// 因为他用了 context.dependOnInheritedWidgetOfExactType<AppColor>();
// 会将自己注册到 AppColor
ColorfulContainer(),
ChangeStateButton(),
Text("This Text Should Not Rebuild"),
],
)
],
);
}
}
class ColorfulContainer extends StatelessWidget {
ColorfulContainer({Key? key}) : super(key: key);
Widget build(BuildContext context) {
var appColor = AppColor.of(context);
print(
"_ColorfulContainerState appColor?.color:${appColor?.color} appColor:${appColor.hashCode}");
return Container(
color: appColor?.color,
height: 100,
child: Text("hello color ${appColor?.color}"),
);
}
}
class ChangeStateButton extends StatefulWidget {
@override
State<ChangeStateButton> createState() => _ChangeStateButtonState();
}
class _ChangeStateButtonState extends State<ChangeStateButton> {
@override
Widget build(BuildContext context) {
return MaterialButton(
onPressed: () {
// 注意下面这个方法,只是查找到 InheritedWidget 的引用,并没有注册依赖
// 所以当 InheritedWidget 变化的时候并不会触发此控件重建
// 因为每次 onColorChanged 时 AppColor 都会重建,所以需要在这里获取最新的
var appColor = context.findAncestorWidgetOfExactType<AppColor>();
print(
"_ChangeStateButtonState appColor?.color:${appColor?.color} appColor:${appColor.hashCode}");
var color = appColor?.color;
var newColor = color == Colors.teal ? Colors.blueAccent : Colors.teal;
appColor?.onColorChanged?.call(newColor);
print(
"_ChangeStateButtonState onPressed appColor?.color:${appColor?.color} appColor:${appColor.hashCode}");
},
child: Text("Change State Button, Shlould NOT Rebuild"),
);
}
}
3、接下来实现一种基础的使用InheritedWidget
的方法,这种方法会在InheritedWidget
更新的时候,rebuilt InheritedWidget
下面的所有子类,无论他们是否使用到了InheritedWidget
(原因是上面说到的 Flutter rebuild 的机制导致的,实际上InheritedWidget
本身只标记了ColorfulContainer
为dirty
)。
class AlwaysRebuildWidget extends StatefulWidget {
final Color color;
AlwaysRebuildWidget({Key? key, required this.color}) : super(key: key);
@override
State<AlwaysRebuildWidget> createState() => _AlwaysRebuildWidgetState();
}
class _AlwaysRebuildWidgetState extends State<AlwaysRebuildWidget> {
late Color _color;
var child = NoName();
@override
void initState() {
super.initState();
_color = widget.color;
}
@override
Widget build(BuildContext context) {
// 这种写法,AppColor 的_dependents 也只有 1 个。ColorfulContainer(dependencies: [AppColor])
// 所以每次 setState 引起 AlwaysRebuildWidget 重新绘制,引起 AppColor 重新创建,本应该会重建 ColorfulContainer
// 但是因为 build 方法重新执行了一次,所以 AppColor 和整个 NoName 都被重建,
print("AlwaysRebuildWidget build${widget.hashCode}");
return AppColor(
color: _color,
onColorChanged: (color) {
setState(() {
_color = color;
});
},
// 这种写法,AppColor 在 updateChild 的时候会判断 widget.child 与_child.widget 的 NoName 不一致
// (这是因为,AppColor 在 notifyClients 的时候修改了 NoName 的 child 之一 ColorfulContainer 为 dirty)
// 从而会更新 NoName,导致 NoName 下面所有的子 Widget 全部重新绘制
child: NoName());
// 按照上面分析的逻辑,在这里加上 const,那么依旧用的是之前的 NoName,就不会 repaint 整个的 NoName 了
// child: const NoName());
}
}
4、接下来实现一种使用InheritedWidget
的方法,当InheritedWidget
更新的时候,只会更新那些在InheritedWidget
这里注册依赖了的Widget
。
class SelectiveRebuildWidget extends StatefulWidget {
final Widget child;
final Color color;
SelectiveRebuildWidget({Key? key, required this.child, required this.color})
: super(key: key) {}
@override
State<SelectiveRebuildWidget> createState() => _SelectiveRebuildWidgetState();
}
class _SelectiveRebuildWidgetState extends State<SelectiveRebuildWidget> {
late Color _color;
@override
void initState() {
super.initState();
_color = widget.color;
}
@override
Widget build(BuildContext context) {
print(
"SelectiveRebuildWidget build${widget.child.hashCode} ${widget.hashCode}");
return AppColor(
color: _color,
onColorChanged: (color) {
setState(() {
_color = color;
});
},
// 这里的 AppColor 的_dependents 只有 1 个。ColorfulContainer(dependencies: [AppColor])
// 因为 setState 不会重新创建 SelectiveRebuildWidget,所以 widget.child 也没有被重新
// 创建(但是重新绘制了,导致 AppColor 也重新绘制)
// 所以 AppColor 的 child 还是之前的,按照 InheritedWidget 的规则,只有 ColorfulContainer 重新绘制了
child: widget.child,
);
}
}
参考资料
Using Inherited Widget In Flutter
【Flutter 学习】之 Widget 数据共享之 InheritedWidget 梁飞宇
Managing Flutter Application State With InheritedWidgets