Flutter Notification 自底向上的数据传递

2,157 阅读3分钟

Flutter Notification 自底向上的数据传递

Notification和InheritedWidget作用相反,InheritedWidget是自上向下传递数据,Notification是自下向上传递数据.

之前已经写过一篇InheritedWidget的文章了,有兴趣的可以回顾一下. InheritedWidget源码解析

NotificationListener

NotificationListener的代码很简单,只有一个方法,onNotification,处理通知事件,并通过返回布尔值来决定该事件是否继续向上传递,这个方法需要用户自定义.

onNotification

final NotificationListenerCallback<T>? onNotification;

Notification

Notification也只有一个方法dispatch.

dispatch的方法很简单,就是调用传入BuildContext的dispatchNotification方法.

void dispatch(BuildContext? target) { 
    target?.dispatchNotification(this); 
}

下面看一下dispatchNotification方法.它是调用了_notificationTreedispatchNotification方法。

@override
void dispatchNotification(Notification notification) {
  _notificationTree?.dispatchNotification(notification);
}

_notificationTree是每个element都有的一个对象,这里看一下它的初始化。正常是赋值了父element的_notificationTree对象.

void attachNotificationTree() {
  _notificationTree = _parent?._notificationTree;
}

但是要是element添加了NotifiableElementMixinmixin,它就会创建一个新的_NotificationNode

mixin NotifiableElementMixin on Element {
  bool onNotification(Notification notification);

  @override
  void attachNotificationTree() {
    _notificationTree = _NotificationNode(_parent?._notificationTree, this);
  }
}

NotificationListener组件的element就添加了这个NotifiableElementMixinmixin

  const NotificationListener({
    super.key,
    required super.child,
    this.onNotification,
  });

  final NotificationListenerCallback<T>? onNotification;

  @override
  Element createElement() {
    return _NotificationElement<T>(this);
  }
}

class _NotificationElement<T extends Notification> extends ProxyElement with 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) {
    // Notification tree does not need to notify clients.
  }
}

从这里我们可以推断出每一层NotificationListener它都有一个属于自己的_NotificationNode它的作用就是可以判断是否要继续向上通知。看到这里我们应该明白了_notificationTree是怎么生成的,我们再来看一下它的是怎么向上通知的。_notificationTree会调用自身的dispatchNotification方法,这个方法会调用onNotification方法。

void dispatchNotification(Notification notification) {
  if (current?.onNotification(notification) ?? true) {
    return;
  }
  parent?.dispatchNotification(notification);
}

而这里它则是调用了上面_NotificationElementonNotification方法,这里调用的就是NotificationListener里我们实现的onNotification方法,不过它会先判断我们的范型是否一致,要是范型一致的话就调用,不一致的话就继续调用父组件的dispatchNotification方法。

  @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;
  }

SizeChangedLayoutNotification

这是一个监听组件Size改变的Notification,比如我们想在父组件获取子组件的大小就可以用这个. 下面的代码是在官方的SizeChangedLayoutNotifier加了一些修改.

class SizeChangedLayoutNotification extends Notification {
  final Size size;

  SizeChangedLayoutNotification(this.size);
}

class SizeChangedLayoutNotifier extends SingleChildRenderObjectWidget {
  const SizeChangedLayoutNotifier({
    Key key,
    Widget child,
  }) : super(key: key, child: child);

  @override
  _RenderSizeChangedWithCallback createRenderObject(BuildContext context) {
    return _RenderSizeChangedWithCallback(
        onLayoutChangedCallback: (Size size) {
          SizeChangedLayoutNotification(size).dispatch(context);
        }
    );
  }
}

class _RenderSizeChangedWithCallback extends RenderProxyBox {
  _RenderSizeChangedWithCallback({
    RenderBox child,
    @required this.onLayoutChangedCallback,
  }) : assert(onLayoutChangedCallback != null),
        super(child);

  final ValueChanged<Size> onLayoutChangedCallback;

  Size _oldSize;

  @override
  void performLayout() {
    super.performLayout();
    if (size != _oldSize)
      onLayoutChangedCallback(size);
    _oldSize = size;
  }
}

使用代码.这里只做演示,SizeChangedLayoutNotification和官方的重名了,官方的widget是只发通知,不会传递size,如果想实际使用下面的代码,要先改一下组件的名称.

NotificationListener<SizeChangedLayoutNotification>(
  onNotification: (SizeChangedLayoutNotification notification) {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      setState(() {
        childSize = notification.size;
      });
    });
    return true;
  },
  child: SizeChangedLayoutNotifier(
    child: child
  ),
)

每当子child大小发生改变的时候我们就会收到SizeChangedLayoutNotification的通知.