在Flutter中,UI是由不同粒度的Widget组成,这也导致了一些数据需要在Widget间传递。一般情况下,都是通过属性来进行数据的传递。但如果涉及到跨层传递时,属性可能需要跨越很多层才能传递给子组件,导致中间很多并不需要这个属性的组件,也得接收其子Widget的数据,繁琐且冗余。
所以这时候就需要使用其他方案来进行跨层传输,目前主要有InheritedWidget、Notification及EventBus三种方案来实现数据的跨层传输。
1、InheritedWidget
InheritedWidget主要用于底层Widget向上层Widget获取数据,也就是数据的传递方向是从由父Widget传递给子Widget,也就是说两个Widget必须有父子关系才能使用InheritedWidget。
1.1、InheritedWidget的使用
下面来看InheritedWidget的使用,如下。
//创建一个继承自InheritedWidget的类
class CountContainer extends InheritedWidget {
//获取类型是CountContainer的InheritedWidget,时间复杂度是O(1)
static CountContainer of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<CountContainer>();
final int count;
CountContainer({Key key, @required this.count, @required Widget child})
: super(key: key, child: child);
@override
bool updateShouldNotify(covariant CountContainer oldWidget) =>
count != oldWidget.count;
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
//将CountContainer作为根节点,并使用0作为初始化count
return CountContainer(count: 0, child: Counter());
}
}
//CountContainer的使用
class Counter extends StatelessWidget {
@override
Widget build(BuildContext context) {
//获取InheritedWidget节点
CountContainer state = CountContainer.of(context);
return Scaffold(
appBar: AppBar(title: Text("InheritedWidget demo")),
body: Text(
'You have pushed the button this many times: ${state.count}',
),
);
}
}
在上面代码中,有一个问题。那就是count的值是无法修改的。但如果要修改count的值,可以使用ChangeNotifier来进行值的更新。
class CountContainer<T extends ValueNotifier> extends InheritedNotifier {
static CountContainer of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<CountContainer>();
const CountContainer({
Key key,
this.notifier,
@required Widget child,
}) : assert(child != null),
super(key: key, child: child);
final T notifier;
}
class _MyHomePageState extends State<MyHomePage> {
//ValueNotifier继承自ChangeNotifier
ValueNotifier _valueNotifier;
int count = 0;
@override
void initState() {
//创建一个ValueNotifier对象
_valueNotifier = new ValueNotifier(0);
super.initState();
}
@override
Widget build(BuildContext context) {
return CountContainer(
notifier: _valueNotifier,
child: GestureDetector(
onTap: () {
count++;
//值的更新
_valueNotifier.value = count;
},
child: Counter(),
),
);
}
}
class Counter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("InheritedWidget demo")),
body: Text(
'You have pushed the button this many times: ${CountContainer.of(context).notifier.value}',
),
);
}
}
通过上面代码,就能够使用InheritedWidge+ChangeNotifier来获取父Widget中可变化的数据。
provider中数据更新就是通过InheritedWidge+ChangeNotifier来实现的。
1.2、InheritedWidget的实现原理
再来看InheritedWidget的实现原理。它的核心实现再其InheritedElement中,代码如下。
class InheritedElement extends ProxyElement {
/// Creates an element that uses the given widget as its configuration.
InheritedElement(InheritedWidget widget) : super(widget);
@override
InheritedWidget get widget => super.widget as InheritedWidget;
//存储Element
final Map<Element, Object> _dependents = HashMap<Element, Object>();
@override
void _updateInheritance() {
//获取父Element的_inheritedWidgets
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
//将InheritedElement以widget.runtimeType为key添加到_inheritedWidgets中
_inheritedWidgets[widget.runtimeType] = this;
}
@protected
Object getDependencies(Element dependent) {
//根据Element从_dependents中获取存储的值
return _dependents[dependent];
}
@protected
void setDependencies(Element dependent, Object value) {
//将Element添加到_dependents中,value可为null
_dependents[dependent] = value;
}
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
//调用Element的didChangeDependencies方法,在didChangeDependencies方法中会重新绘制dependent所对应的Widget
dependent.didChangeDependencies();
}
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
@override
void notifyClients(InheritedWidget oldWidget) {
//根据_dependents来遍历
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
}
abstract class Element extends DiagnosticableTree implements BuildContext {
//...
void _updateInheritance() {
//将父Element中_inheritedWidgets的值赋给当前Element的_inheritedWidgets
_inheritedWidgets = _parent?._inheritedWidgets;
}
//...
}
先来看属性_inheritedWidgets,它是Element中的一个Map。如果当前Element是InheritedElement,则将当前Element添加到_inheritedWidgets中,否则就会将其父Element中的_inheritedWidgets的数据拷贝给当前Element的_inheritedWidgets。也就是每个Element中会存储当前Element到根Element中所有的InheritedElement对象,所以这也是dependOnInheritedWidgetOfExactType的时间复杂度为O(1)的原因。
abstract class Element extends DiagnosticableTree implements BuildContext {
//...
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
//将当前Element添加到`InheritedElement`的`_dependents`中
ancestor.updateDependencies(this, aspect);
//返回一个InheritedWidget对象
return ancestor.widget;
}
//...
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
//根据T的类型从_inheritedWidgets中获取InheritedElement,时间复杂度为O(1)
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
//...
}
在通过dependOnInheritedWidgetOfExactType获取InheritedWidget时,也会把当前Element添加到InheritedElement的_dependents中。
当调用InheritedElement的notifyClients方法时,就会遍历其_dependents,然后执行_dependents中Element的didChangeDependencies方法来更新UI。
abstract class Element extends DiagnosticableTree implements BuildContext {
//...
@mustCallSuper
void didChangeDependencies() {
//将当前Element标记为脏,在setState中最终调用的也是markNeedsBuild方法。
markNeedsBuild();
}
//...
}
2、Notification
Notification主要用于底层Widget向上层Widget传递数据,也就是数据方向的传递方向是从子Widget传递给父Widget。也就是两个Widget必须有父子关系才能使用Notification。
2.1、Notification的使用
Notification需要与NotificationListener搭配使用。
//创建一个Notification对象
class CountContainer extends Notification {
final int count;
CountContainer(this.count);
}
class _MyHomePageState extends State<MyHomePage> {
int count = 0;
int num = 0;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("InheritedWidget demo")),
body: NotificationListener<CountContainer>(
//接受子widget传递过来的值
onNotification: (CountContainer container) {
setState(() {
num = container.count + 1;
});
//返回false则表示还可以继续向上传递数据
return false;
},
child: Column(
children: [
Text("当前num的值:$num"),
NotificationListener<CountContainer>(
child: Column(
children: [Text("当前count的值:$count"), Counter()],
),
//接受子widget传递过来的值
onNotification: (CountContainer container) {
setState(() {
count = container.count;
});
//返回false则表示还可以继续向上传递数据
return false;
},
)
],
),
),
);
}
}
class Counter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TextButton(
child: Text("更新UI"),
onPressed: () {
//向父Widget传递数据
CountContainer(1).dispatch(context);
},
);
}
}
使用很简单,这里要注意一点的是onNotification对应函数的返回值。如果返回值为false,则表示数据还可以继续向上传递,直到根Widget。否则就到当前Widget为止。
2.2、Notification的实现原理
Notification的实现原理就很简单了。就是从当前Widget出发,向上遍历,找到对应的NotificationListener并把数据交给NotificationListener的_dispatch方法进行分发。代码实现如下。
abstract class Notification {
const Notification();
@protected
@mustCallSuper
bool visitAncestor(Element element) {
if (element is StatelessElement) {
final StatelessWidget widget = element.widget;
if (widget is NotificationListener<Notification>) {
//找到NotificationListener并交给其的_dispatch方法来处理数据
if (widget._dispatch(this, element)) // that function checks the type dynamically
return false;
}
}
return true;
}
void dispatch(BuildContext target) {
//从当前Widget向上遍历查找指定的NotificationListener,时间复杂度为O(n)
target?.visitAncestorElements(visitAncestor);
}
}
visitAncestorElements就是向上遍历Element的实现,而visitAncestor则是判断当前Widget是否是指定的NotificationListener的实现。
abstract class Element extends DiagnosticableTree implements BuildContext {
//...
@override
void visitAncestorElements(bool visitor(Element element)) {
Element ancestor = _parent;
//如果visitor返回true且ancestor不为null,则表示继续向上遍历
while (ancestor != null && visitor(ancestor))
ancestor = ancestor._parent;
}
//...
}
visitAncestorElements的实现很简单,如上。所以查找指定NotificationListener的时间复杂度为O(n)。
3、EventBus
InheritedWidget与Notification必须要求两个Widget有父子关系才可进行数据的传输。但如果两个Widget是兄弟关系的话,那么就无法使用InheritedWidget及Notification。这时候就可以使用EventBus来进行数据的传输,当然具有父子关系的Widget也可以使用EventBus来进行数据的传输。
3.1、EventBus的使用
首先需要导入EventBus,如下。
event_bus: ^1.1.1
导入成功后,下面就可以直接来使用EventBus。
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("EventBus Demo"),
),
body: Column(
children: [
//接受消息Widget
MessageWidget(),
//发送消息Widget
OtherWidget(),
],
),
);
}
}
class Message {
final String text;
const Message(this.text);
}
class MessageWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => MessageWidgetState();
}
EventBus eventBus = new EventBus();
//展示消息Widget
class MessageWidgetState extends State<MessageWidget> {
String text = "没任何消息";
StreamSubscription subscription;
@override
void initState() {
subscription = eventBus.on<Message>().listen((event) {
setState(() {
text = event.text;
});
});
super.initState();
}
@override
void dispose() {
//不取消则存在内存泄漏
subscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text("当前消息:$text");
}
}
//发送消息Widget
class OtherWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
eventBus.fire(Message("消息测试"));
},
child: Text("发送消息"));
}
}
在上面代码中,MessageWidget与OtherWidget是兄弟关系,所以无法使用InheritedWidget与Notification来实现两个Widget间的通信,而使用EventBus正好实现了两者间的通信。
EventBus是基于Stream来实现的,关于其实现原理可以参考Flutter完整开发实战详解(十一、全面深入理解Stream)这篇文章。
4、总结
本文主要是对Flutter中Widget间通信做了一个介绍。虽然目前都推荐使用Provider,但还是需要了解本文中的三种方案,这样才能更好的使用provider及阅读provider源码。当然本文也是分析provider实现原理的前篇。