Flutter UI 绘制与InheritedWidget解析

Flutter UI 绘制与InheritedWidget解析

Flutter的Widget分为StatefulWidgetStatelessWidget ,二者都继承自Widget

此外还有一种用来传输数据的Widget——InheritedWidget,与上述两者不太一样的是,他的继承关系是:InheritedWidgetProxyWidgetWidget

Flutter的渲染流程如图:

flutter_widget_element_renderobject_relationship

可以简单理解为, Widget是配置信息,Element代表在树中详细的位置,而RenderObject则是实际渲染的对象。

StatelessWidgetStatefulWidget在创建之后就不会再变化,而StatefulWidget因为有State,所以可以在State调用setState()方法之后,重新执行Statebuild()方法,从而更新界面。

如果Widgetconst的,那么他就不会被rebuild

Widget Rebuild的过程

以StatefulWidget为例:

flutter_render_flow_chart

  1. 调用setState()方法,会调用对应的ElementmarkNeedsBuild() 方法,通过BuildOwnerscheduleBuildFor(Element element) 方法将当前Element标记为dirty,以便在下次屏幕刷新时安排rebuilt

  2. 下一帧屏幕刷新,调用BuildOwnerbuildScope(Element context, [ VoidCallback? callback ]) 方法。这个方法会遍历_dirtyElements 中所有dirtyelement执行element.rebuild(); 方法,在其内部调用了ElementperformRebuild() 方法。

  3. ElementperformRebuild() 方法因各个Element的实现而异:

    1. StatelessElementInheritedElement:与父类ComponentElement 保持一致

    2. StatefulElement :判断有需要时调用state.didChangeDependencies(); ,其余与父类ComponentElement 保持一致

ComponentElementperformRebuild() 主要做了2件事:
(1)built = build(); ;(2)_child = updateChild(_child, built, slot);

在这其中build()

  1. StatelessElement:build() => widget.build(this);
  2. StatefulElement : build() => state.build(this);
  3. 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); 方法会触发Widgetrebuild()

这样就完成了一次Rebuild。

InheritedWidget的Rebuild过程

InheritedWidget是持有状态的Widget,他的子Widget可以通过他来获取这些状态。

一般来说,InheritedWidget持有的状态是final的,如果要更新状态,就需要在其外部包裹一个StatefulWidget,通过StatefulWidgetState.setState()来触发InheritedWidget重建(实际上InheritedElement没有重新创建),从而更新那些依赖了InheritedWidget的子Widget

下图是一个被StatefulWidget包裹的InheritedWidgetsetSate(){}方法执行后的流程图:

flutter_render_flow_chart_with_inheritedwidget

当外层StatefulWidgetElement执行到updateChild(child,build,solt);会调用InheritedElementupdate() 方法。

这个方法内部会调用updated(oldWidget) 方法,在内部通过notifyClients(oldWidget); 方法,通知原先的InheritedElement_dependents ,将其标记为dirty,准备rebuild

在此之后,update()方法还会将当前Element标记为dirty,通过调用rebuild(); 执行performRebuild();

performRebuild()方法中:

  • built = build(); 中的build方法:build() => widget.child; 实际上取了widgetchild
  • 然后执行_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 一个持有colorInheritedWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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本身只标记了ColorfulContainerdirty)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 梁飞宇

InheritedWidget confusion

Managing Flutter Application State With InheritedWidgets

Does using const in the widget tree improve performance?

StatefulWidget