在Flutter中,ProxyWidget 是一个特殊的widget,它不创建Widget,而是关联子组件和父组件,为它们之间的交流提供渠道,在适当的时候提供额外的功能。比如可以使用ProxyWidget来监视或更改子widget的约束、样式或属性,而无需直接修改子widget本身。它就是提供了一种机制,可以在不更改子widget本身的情况下,对其进行干预或者在其周围构建其他逻辑。
对应的,ProxyWidget 的 element 是 ProxyElement。
ProxyWidget 有三个直接子类:
1、ParentDataWidget:在实际开发中,常用于视图数据的传递
2、InheritedWidget:在实际开发中,常用于业务数据的传递
3、NotificationListener:通知冒泡,在实际开发中,常用于滚动监听
ParentDataWidget
引用注释的一句话:
css
A [ParentDataWidget] is specific to a particular kind of [ParentData].
父组件会用存储在子组件的 RenderObject 的 ParentData 来对子组件进行一些处理,比如布局、绘制。
ParentDataWidget 正是用来配置子组件的 RenderObject 的 ParentData 的。
可以类比 Android 中的 ViewGroup.LayoutParams 。
那么可以从读、写两个角度分析这里的数据流转:
写:即设置 ParentData:
组件在 mount 之后,会依次执行 attachRenderObject 、_updateParentData 方法,最后执行到 applyParentData:
arduino
@protected
void applyParentData(RenderObject renderObject);
这里以 Positioned 组件为例,它是一个 StackParentData 类型的 ParentDataWidget
scala
sdk:
class Positioned extends ParentDataWidget<StackParentData>
demo:
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: [
Positioned(
top: 50,
left: 50,
child: _box(),
),
],
),
),
);
}
对于 Positioned 组件的 applyParentData 方法:
ini
@override
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is StackParentData);
final StackParentData parentData = renderObject.parentData! as StackParentData;
bool needsLayout = false;
if (parentData.left != left) {
parentData.left = left;
needsLayout = true;
}
// ... 省略其它属性
if (needsLayout) {
final AbstractNode? targetParent = renderObject.parent;
if (targetParent is RenderObject) {
targetParent.markNeedsLayout();
}
}
}
当第一次执行时,子组件的 parentData.left 为 null,那么给它设置新数据 50,标记 needsLayout 为 true,最后 markNeedsLayout() ,等待更新ui。
后续触发更新时,会触发 ProxyElement 的 notifyClients 方法,最后还是走到 applyParentData 方法,如果新旧数据不一致,再次触发 markNeedsLayout() 。
读:即获取 ParentData:
向上寻找 Positioned 的父组件 Stack 的 performLayout 方法:
ini
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
_hasVisualOverflow = false;
size = _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
assert(_resolvedAlignment != null);
RenderBox? child = firstChild;
while (child != null) {
/// 获取到 StackParentData
final StackParentData childParentData = child.parentData! as StackParentData;
if (!childParentData.isPositioned) {
childParentData.offset = _resolvedAlignment!.alongOffset(size - child.size as Offset);
} else {
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment!) || _hasVisualOverflow;
}
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
}
在 RenderStack 的 performLayout 中,容易找到,通过 child 的 parentData 属性,获取到了 StackParentData。如果子组件被 Positioned 组件包裹,那么它会执行
layoutPositionedChild,
源码这里不贴了,逻辑就是通过 StackParentData 携带来的 上、下、左、右、宽、高等数据计算出约束和偏移,通过 layout 方法进行布局。
由此,这里可以得到自定义 ParentDataWidget 的一点思路:
dart
demo:
class MyPosition extends ParentDataWidget<StackParentData> {
const MyPosition({
super.key,
required super.child,
required this.top,
});
final double top;
// 仅提供简略逻辑。这里将top设置为输入值的2倍
@override
void applyParentData(RenderObject renderObject) {
final StackParentData parentData =
renderObject.parentData! as StackParentData;
parentData.top = 2 * top;
(renderObject.parent as RenderObject).markNeedsLayout();
}
// 给哪个组件提供parentData信息。仅作为debug信息。
@override
Type get debugTypicalAncestorWidgetClass => Stack;
}
实际结果影响了布局时的数据。
更进一步:自定义 ProxyWidget 、ProxyElement
scala
class MyProxyWidget extends ProxyWidget {
const MyProxyWidget({
super.key,
required this.width,
required super.child,
});
final int width;
@override
Element createElement() {
return MyProxyElement(this);
}
}
class MyProxyElement extends ProxyElement {
MyProxyElement(super.widget);
@override
void notifyClients(MyProxyWidget oldWidget) {
if ((widget as MyProxyWidget).width != oldWidget.width) {
print("======> 数据变更,去做事");
}
}
}
InheritedWidget
最早接触 InheritedWidget 是在学习状态管理的一些框架中,比如 Provider、Bloc等,都是对于 InheritedWidget 的封装。子组件可以通过 context 向上搜索父组件,找到对应InheritedWidget 提供的数据。
典型的实现是:继承 InheritedWidget 组件后,实现maybeOf 和 of 方法
scala
demo:
class FrogColor extends InheritedWidget {
const FrogColor({
super.key,
required this.color,
required super.child,
});
final Color color;
static FrogColor? maybeOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FrogColor>();
}
static FrogColor of(BuildContext context) {
final FrogColor? result = maybeOf(context);
assert(result != null, 'No FrogColor found in context');
return result!;
}
@override
bool updateShouldNotify(FrogColor oldWidget) => color != oldWidget.color;
}
典型使用是:在子组件中调用
less
demo:
@override
Widget build(BuildContext context) {
return Scaffold(
body: FrogColor(
color: Colors.green,
child: Builder(
builder: (BuildContext innerContext) {
// 1、必须在子组件中调用
// 2、这里通过 Builder 组件构造了一个子组件环境
final textColor = FrogColor.of(innerContext).color;
return Text(
'Hello Frog',
style: TextStyle(color: textColor),
);
},
),
),
);
}
InheritedWidget 是一个典型的观察者模式的实现,那么可以从以下两方面分析:
1、注册监听
以上面的 dependOnInheritedWidgetOfExactType 方法为切入口:
dart
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
容易观察到,InheritedWidget 是从 _inheritedWidgets 成员中获取的,
ini
PersistentHashMap<Type, InheritedElement>? _inheritedWidgets;
_inheritedWidgets 是 Element 的一个成员,widget 在执行 mount 方法中,会执行 _updateInheritance() 方法,这里给 _inheritedWidgets 赋值,
ini
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
final PersistentHashMap<Type, InheritedElement> incomingWidgets =
_parent?._inheritedWidgets ?? const PersistentHashMap<Type, InheritedElement>.empty();
_inheritedWidgets = incomingWidgets.put(widget.runtimeType, this);
}
map 以 widget.runtimeType 为 Key,所以在多层嵌套的时候,获取的数据只会来自离当前组件最近的组件。
此时,如果找到了对应类型的 InheritedElement ,那么通过 dependOnInheritedElement 方法向它注册监听,将自己(this)加入到 _dependents 中:
dart
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget as InheritedWidget;
}
final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
到这里,完成了注册监听的操作。额外的,观察到:
java
@override
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
return ancestor;
}
getElementForInheritedWidgetOfExactType 与 dependOnInheritedWidgetOfExactType 的区别:前者没有注册监听。
2、获取数据改变,通知观察者
数据改变,界面发生更新,唯一的方式就是去执行 Element 的 performRebuild() 方法,重新创建一个新的Widget。这里有必要贴出部分注释,讲一下 didChangeDependencies方法。
在 StatefulWidget 中, State 对象有个 didChangeDependencies 方法,在多次重建时,它会在数据发生变化时执行,以下方法依次执行:
typescript
StatefulWidget 部分注释:
/// The second category is widgets that use [State.setState] or depend on
/// [InheritedWidget]s. These will typically rebuild many times during the
/// application's lifetime, and it is therefore important to minimize the impact
/// of rebuilding such a widget. (They may also use [State.initState] or
/// [State.didChangeDependencies] and allocate resources, but the important part
/// is that they rebuild.)
InheritedElement 的 notifyClients 方法部分注释:
/// Notifies all dependent elements that this inherited widget has changed, by
/// calling [Element.didChangeDependencies].
///
/// This method must only be called during the build phase. Usually this
/// method is called automatically when an inherited widget is rebuilt, e.g.
/// as a result of calling [State.setState] above the inherited widget.
InheritedElement.updated // 数据更新后的执行
@override
void updated(InheritedWidget oldWidget) {
if ((widget as InheritedWidget).updateShouldNotify(oldWidget)) { // 重建方法的判断
super.updated(oldWidget); // 执行父类的updated
}
}
ProxyElement.updated
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget); // 父类实现
}
InheritedElement.notifyClients
/// 忽略了 assert
@override
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent); // 遍历上述 注册监听 时的 _dependents
}
}
InheritedElement.notifyDependent
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies(); // 这里执行原组件的 didChangeDependencies
}
StatefulElement.didChangeDependencies
@override
void didChangeDependencies() {
super.didChangeDependencies(); // 调用了 super.
_didChangeDependencies = true; // 这里标识没有影响下面的 super.performRebuild();
}
Element.didChangeDependencies
/// 忽略了 assert
@mustCallSuper
void didChangeDependencies() {
markNeedsBuild(); // 标记需要重建,等待下一帧
}
StatefulElement.performRebuild
@override
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies(); // 调用了state的didChangeDependencies方法,回到了业务里
_didChangeDependencies = false;
}
super.performRebuild(); // 重建
}
以上描述了两个逻辑:
a、向父组件 InheritedWidget 注册了的子组件且满足 updateShouldNotify 的条件,子组件的 didChangeDependencies 才会被执行。
b、如果使用 setState() 方法, build 方法不管注册与否,都会调用,如果要减少 build 方法执行,需要实现缓存,涉及到状态管理逻辑的处理。
scala
demo:
class OneChild extends StatefulWidget {
const OneChild({
super.key,
required this.color,
});
final Color color;
@override
State<OneChild> createState() => _OneChildState();
}
class _OneChildState extends State<OneChild> {
@override
Widget build(BuildContext context) {
// 如果不注册 InheritedWidget ,
// 即使通过 setState 改变了数据,didChangeDependencies也不会执行。
// final textColor = FrogColor.of(context).color;
return Text(
'Hello Frog',
style: TextStyle(color: widget.color),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("======> didChangeDependencies");
}
}
接下来继续讲 InheritedWidget 实现局部刷新的原理。这里可以先对比一下:
csharp
ProxyElement:
@override
Widget build() => (widget as ProxyWidget).child;
StatefulElement:
@override
Widget build() => state.build(this);
StatelessElement:
@override
Widget build() => (widget as StatelessWidget).build(this);
ProxyElement 的 build 方法直接返回了 child。
ini
省略部分逻辑
@protected
@pragma('vm:prefer-inline')
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
newChild = child; // 1 直接赋值
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
child.update(newWidget); // 2 更新重建
} else {
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot); // 3 新建
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
上述说到:InheritedElement.updated 触发了后续的更新重建,它的调用时从:
Element.updateChild -> ProxyElement.update -> InheritedElement.updated 来的。
这里,通过源码的 if-else 判断,至少 child.widget != newWidget 时,才会执行 child.update(newWidget);。
而 ProxyElement 并没有提供 newWidget,没有新建,只是提供了子 widget。
NotificationListener
典型用法:
scala
// 1.定义一个通知
class NumNotification extends Notification {
int notifyNum;
NumNotification({
required this.notifyNum,
});
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: NotificationListener<NumNotification>(
onNotification: (notification) { // 2.设置监听回调
print("======> ${notification.notifyNum}");
return true;
},
child: SizedBox(
width: 100,
height: 100,
child: ColoredBox(
color: Colors.red,
child: Builder(
builder: (context) {
return GestureDetector(
onTap: () {
// 3.dispatch 分发通知
NumNotification(notifyNum: 1).dispatch(context);
},
);
},
),
),
),
),
),
);
}
}
相对于ParentDataWidget和InheritedWidget的原理来讲,NotificationListener比较简单,这里直接贴出_NotificationElement、NotifiableElementMixin、_NotificationNode的源码,并增加了关键注释:
scala
class _NotificationElement<T extends Notification> extends ProxyElement
with NotifiableElementMixin { // 混入 NotifiableElementMixin
_NotificationElement(NotificationListener<T> super.widget);
@override
bool onNotification(Notification notification) {
final NotificationListener<T> listener = widget as NotificationListener<T>;
if (listener.onNotification != null && notification is T) { // 有设置对应类型的监听回调
return listener.onNotification!(notification); // 处理收到的通知
}
return false;
}
@override
void notifyClients(covariant ProxyWidget oldWidget) {
// sdk:
// Notification tree does not need to notify clients.
// custom:
// sdk注释说在通知里,notifyClients不需要有实际操作,
// 这也是NotificationListener与ParentDataWidget、InheritedWidget的区别之一,
// 后两者都需要依赖notifyClients方法做数据更新。
}
}
mixin NotifiableElementMixin on Element {
/// Called when a notification of the appropriate type arrives at this
/// location in the tree.
///
/// Return true to cancel the notification bubbling. Return false to
/// allow the notification to continue to be dispatched to further ancestors.
bool onNotification(Notification notification);
// Notification tree 是由一个个 _NotificationNode 组成的,
// attachNotificationTree() 方法会在widget 在执行 mount 方法中 执行,
//(与InheritedWidget 的 _updateInheritance();方法执行时机一致。)
@override
void attachNotificationTree() {
_notificationTree = _NotificationNode(_parent?._notificationTree, this);
}
}
class _NotificationNode {
_NotificationNode(this.parent, this.current);
NotifiableElementMixin? current;
// 当前的 _NotificationNode 可以拿到父节点的 _NotificationNode
_NotificationNode? parent;
// 处理通知
void dispatchNotification(Notification notification) {
// 这里的if判断执行了两步操作
// 1 处理 onNotification 的业务回调
// 2 根据回调结果,如返回 true,不再向上冒泡,如返回 false,继续冒泡给父组件处理
if (current?.onNotification(notification) ?? true) {
return;
}
parent?.dispatchNotification(notification);
}
}