大道同源, 其实Flutter的事件分发跟安卓类似。 下面就详细介绍下Flutter的事件分发体系:
1、 Flutter所有事件源头是 hooks.dart文件的_dispatchPointerDataPacket函数, 接收屏幕的点击、滑动等等各种事件。 类似于安卓的ViewRootImpl.java接收native层的数据。
2、gestures/binding.dart中的GestureBinding是Flutter事件分发的基类, 它的成员函数dispatchEvent、handleEvent和hitTest是核心函数, 从事件队列里按照先入先出方式处理PointerEvent。 处理_dispatchPointerDataPacket发来的数据。
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances();
_instance = this;
ui.window.onPointerDataPacket = _handlePointerDataPacket;
}
3、converter.dart将物理坐标_dispatchPointerDataPacket收到的物理数据PointerDataPacket转换成PointerEvent, 类似于安卓在ViewRootImpl.java将InputEventReceiver收到的InputEvent转换为MotionEvent。
4、recognizer.dart的GestureRecognizer是所有手势识别的基类,
5、rendering/binding.dart的RendererBinding类关联了render树和Flutter引擎, 暂且理解为安卓的Surface。
6、view.dart的RenderView是render树的根节点, 可以理解为安卓的DecorView。
下面详细介绍事件的分发流程:
1、Flutter框架初始化会调用runApp方法, 它的作用是初始化各个binging;其中GestureBinding就是处理事件分发的。 attachRootWidget就是设置根节点, 可以看到真正的根节点是renderview, 也是Flutter事件分发的起点。
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
2、下面看GestureBinding的逻辑。
void _handlePointerEvent(PointerEvent event) {
assert(!locked);
HitTestResult result;
if (event is PointerDownEvent) {
assert(!_hitTests.containsKey(event.pointer));
result = HitTestResult();
hitTest(result, event.position);
_hitTests[event.pointer] = result;
assert(() {
if (debugPrintHitTestResults)
debugPrint('$event: $result');
return true;
}());
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
result = _hitTests.remove(event.pointer);
} else if (event.down) {
result = _hitTests[event.pointer];
} else {
return; // We currently ignore add, remove, and hover move events.
}
if (result != null)
dispatchEvent(event, result);
}
当收到PointerDownEvent时会创建一个HitTestResult实例(跟安卓判断ACTION_DOWN是一个道理,即点击事件都是从DOWN开始的)。 event.position并不是位置,而是类似于数据库主键,区分不同事件的唯一标识, 在这里用_hitTests的key值。
final Map
/// The result of performing a hit test.
class HitTestResult {
/// Creates a hit test result.
///
/// If the [path] argument is null, the [path] field will be initialized with
/// and empty list.
HitTestResult({ List path })
: _path = path ?? [];
/// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
///
/// The first entry in the path is the most specific, typically the one at
/// the leaf of tree being hit tested. Event propagation starts with the most
/// specific (i.e., first) entry and proceeds in order through the path.
Iterable get path => _path;
final List _path;
/// Add a [HitTestEntry] to the path.
///
/// The new entry is added at the end of the path, which means entries should
/// be added in order from most specific to least specific, typically during an
/// upward walk of the tree being hit tested.
void add(HitTestEntry entry) {
_path.add(entry);
}
@override
String toString() => 'HitTestResult(${_path.isEmpty ? "" : _path.join(", ")})';
}
_path保存了PointerEvent的传递路径, 一般_path的第一个HitTestEntry是叶子节点。
当PointerEvent是Up、Cancel时才会调用dispatchEvent函数。 可以看到Flutter是按照插入到path的先后顺序执行handleEvent,即先进先出。
void dispatchEvent(PointerEvent event, HitTestResult result) {
assert(!locked);
assert(result != null);
for (HitTestEntry entry in result.path) {
try {
entry.target.handleEvent(event, entry);
}
再看hitTest函数是把this添加到path里了, 即GestureBinging也会处理点击事件。
void hitTest(HitTestResult result, Offset position) {
result.add(HitTestEntry(this));
}
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event); //观察者模式,向已注册的监听者转发事件
if (event is PointerDownEvent) { //处理手势
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
gestureArena.sweep(event.pointer);
}
}
}
注册事件监听需要hitTest函数, 而Flutter树节点RenderView依赖RenderBox。
class RenderView extends RenderObject with RenderObjectWithChildMixin {
/// Creates the root of the render tree.
///
/// Typically created by the binding (e.g., [RendererBinding]).
///
/// The [configuration] must not be null.
RenderView({
RenderBox child,
重点看RenderBox的hitTest方法,如果子节点或自己能处理事件则添加到result的path中。
bool hitTest(HitTestResult result, { @required Offset position }) {
...
if (_size.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
总结:Flutter事件分发可以理解为注册、处理等2个过程, 相当于按照观察者模式先进先出的处理事件。
HitTestResult的path顺序(处理事件)一般是 叶子节点---父节点---RenderView---WidgetFlutterBinding.
Flutter事件分发的关键点
1、将所有监听该事件的观察者添加的HitTestResult的path里;
2、然后按照FIFO方式从path里逐个执行handleEvent。 如果父子widget都监听同一个事件, 那么子widget先执行handleEvent, 但不阻碍父widget执行handleEvent。 这跟安卓是有区别的。